¿Cuál es la diferencia entre "grupos" y "capturas" en expresiones regulares .NET?

161

Estoy un poco confuso sobre cuál es la diferencia entre un "grupo" y una "captura" cuando se trata del lenguaje de expresión regular de .NET. Considere el siguiente código C #:

MatchCollection matches = Regex.Matches("{Q}", @"^\{([A-Z])\}$");

Espero que esto resulte en una captura única para la letra 'Q', pero si imprimo las propiedades de la devuelta MatchCollection, veo:

matches.Count: 1
matches[0].Value: {Q}
        matches[0].Captures.Count: 1
                matches[0].Captures[0].Value: {Q}
        matches[0].Groups.Count: 2
                matches[0].Groups[0].Value: {Q}
                matches[0].Groups[0].Captures.Count: 1
                        matches[0].Groups[0].Captures[0].Value: {Q}
                matches[0].Groups[1].Value: Q
                matches[0].Groups[1].Captures.Count: 1
                        matches[0].Groups[1].Captures[0].Value: Q

Qué está pasando aquí? Entiendo que también hay una captura para todo el partido, pero ¿cómo entran los grupos? ¿Y por qué no matches[0].Capturesincluye la captura de la letra 'Q'?

Nick Meyer
fuente

Respuestas:

126

No serás el primero que esté confuso al respecto. Esto es lo que el famoso Jeffrey Friedl tiene que decir al respecto (páginas 437+):

Dependiendo de su vista, agrega una nueva dimensión interesante a los resultados del partido, o agrega confusión e hinchazón.

Y más adelante:

La principal diferencia entre un objeto de grupo y un objeto de captura es que cada objeto de grupo contiene una colección de capturas que representan todas las coincidencias intermedias del grupo durante la coincidencia, así como el texto final coincidente por el grupo.

Y unas páginas después, esta es su conclusión:

Después de pasar la documentación de .NET y comprender realmente lo que agregan estos objetos, tengo sentimientos encontrados sobre ellos. Por un lado, es una innovación interesante [..] por otro lado, parece agregar una carga de eficiencia [..] de una funcionalidad que no se utilizará en la mayoría de los casos

En otras palabras: son muy similares, pero ocasionalmente y como sucede, encontrará un uso para ellos. Antes de que te crezca otra barba gris, incluso puedes encariñarte con las Capturas ...


Dado que ni lo anterior ni lo que se dice en la otra publicación realmente parece responder a su pregunta, considere lo siguiente. Piense en Captures como una especie de rastreador de historia. Cuando la expresión regular hace su combinación, pasa a través de la cadena de izquierda a derecha (ignorando el retroceso por un momento) y cuando encuentra un paréntesis de captura coincidente, lo almacenará en $x(x siendo cualquier dígito), digamos$1 .

Los motores regex normales, cuando se deben repetir los paréntesis de captura, descartarán la corriente $1y la reemplazarán con el nuevo valor. No .NET, que mantendrá este historial y lo colocará enCaptures[0] .

Si cambiamos su expresión regular para que tenga el siguiente aspecto:

MatchCollection matches = Regex.Matches("{Q}{R}{S}", @"(\{[A-Z]\})+");

notará que el primero Grouptendrá uno Captures(el primer grupo siempre será la coincidencia completa, es decir, igual a $0) y el segundo grupo se mantendrá {S}, es decir, solo el último grupo coincidente. Sin embargo, y aquí está la captura, si desea encontrar las otras dos capturas, están en Captures, que contiene todas las capturas intermedias para {Q} {R}y {S}.

Si alguna vez se preguntó cómo podría obtener de la captura múltiple, que solo muestra la última coincidencia con las capturas individuales que están claramente allí en la cadena, debe usar Captures.

Una última palabra sobre su pregunta final: la coincidencia total siempre tiene una captura total, no mezcle eso con los grupos individuales. Las capturas solo son interesantes dentro de los grupos .

Abel
fuente
1
a functionality that won't be used in the majority of casesCreo que perdió el bote. A corto plazo (?:.*?(collection info)){4,20}aumenta la eficiencia en más de unos pocos cientos por ciento.
1
@sln, no estoy seguro de a qué te refieres y quién es 'él' (friedl?). El ejemplo que da parece no tener relación con esta discusión o con las expresiones utilizadas. Además, los cuantificadores no codiciosos son muy raramente más eficientes que los cuantificadores codiciosos y requieren conocimiento del conjunto de entrada y pruebas de rendimiento cuidadosas.
Abel
@Abel: aterricé aquí por una pregunta marcada como duplicado de esto. Veo a Friedl citado. Esta publicación es antigua y debe actualizarse para mantenerla moderna. Solo con Dot Net se puede hacer esto, es lo que lo separa de la mayoría de los demás. Desglose: Un ejemplo de grupo global cuantificado sin captura (?:..)+. Empareja perezosamente cualquier cosa .*?hasta una subexpresión de captura (grupo). Continúa en. Dentro de una sola coincidencia, una colección grupal precipita una serie de lo que se necesita. No hay necesidad de encontrar el siguiente, no hay reentrada, por lo que es de 10 a 20 o más veces más rápido.
1
@sln, esta pregunta se trata de algo más y se trata específicamente de una característica .net que no se encuentra en otros motores de expresiones regulares (grupos versus capturas, ver título). No veo nada desactualizado aquí, .net sigue funcionando igual, de hecho, esta parte no ha cambiado en mucho tiempo en .net. El rendimiento no es parte de la pregunta. Sí, la agrupación sin captura es más rápida, pero nuevamente, el tema aquí es lo contrario. Por qué codicioso es más rápido que perezoso se explica en muchos textos en línea y en el libro de friedl, pero OT aquí. ¿Quizás la otra pregunta (¿cuál?) No era un verdadero duplicado?
Abel
2
@Abel - Sé que lo sigo diciendo, pero no lo escuchas. Me molesta esta declaración de Friedl a functionality that won't be used in the majority of cases. De hecho, es la funcionalidad más buscada en regex land. ¿Perezoso / codicioso? ¿Qué tiene eso que ver con mis comentarios? Permite tener una cantidad variable de memorias intermedias de captura. Puede barrer toda la cadena en una sola coincidencia. Si .*?(dog)busca el primer dogcontinuación (?:.*?(dog))+encontrarán todos dog en la cadena completa en una única partida. El aumento de rendimiento es notable.
20

Un grupo es lo que hemos asociado con grupos en expresiones regulares

"(a[zx](b?))"

Applied to "axb" returns an array of 3 groups:

group 0: axb, the entire match.
group 1: axb, the first group matched.
group 2: b, the second group matched.

excepto que estos son solo grupos 'capturados'. Los grupos que no capturan (usando la sintaxis '(?:') No se representan aquí.

"(a[zx](?:b?))"

Applied to "axb" returns an array of 2 groups:

group 0: axb, the entire match.
group 1: axb, the first group matched.

Una captura también es lo que hemos asociado con 'grupos capturados'. Pero cuando el grupo se aplica con un cuantificador varias veces, solo la última coincidencia se mantiene como la coincidencia del grupo. La matriz de capturas almacena todas estas coincidencias.

"(a[zx]\s+)+"

Applied to "ax az ax" returns an array of 2 captures of the second group.

group 1, capture 0 "ax "
group 1, capture 1 "az "

En cuanto a su última pregunta, habría pensado antes de analizar esto que las Capturas serían una serie de capturas ordenadas por el grupo al que pertenecen. Más bien es solo un alias para los grupos [0] .Capturas. Bastante inútil ..

Gerard ONeill
fuente
Explicación clara (y)
Ghasan
19

Esto se puede explicar con un simple ejemplo (e imágenes).

Coincidencia 3:10pmcon la expresión regular ((\d)+):((\d)+)(am|pm)y uso de Mono interactivo csharp:

csharp> Regex.Match("3:10pm", @"((\d)+):((\d)+)(am|pm)").
      > Groups.Cast<Group>().
      > Zip(Enumerable.Range(0, int.MaxValue), (g, n) => "[" + n + "] " + g);
{ "[0] 3:10pm", "[1] 3", "[2] 3", "[3] 10", "[4] 0", "[5] pm" }

Entonces, ¿dónde está el 1? ingrese la descripción de la imagen aquí

Dado que hay varios dígitos que coinciden en el cuarto grupo, solo "llegamos" a la última coincidencia si hacemos referencia al grupo (con un implícito ToString(), es decir). Para exponer las coincidencias intermedias, debemos profundizar y hacer referencia a la Capturespropiedad en el grupo en cuestión:

csharp> Regex.Match("3:10pm", @"((\d)+):((\d)+)(am|pm)").
      > Groups.Cast<Group>().
      > Skip(4).First().Captures.Cast<Capture>().
      > Zip(Enumerable.Range(0, int.MaxValue), (c, n) => "["+n+"] " + c);
{ "[0] 1", "[1] 0" }

ingrese la descripción de la imagen aquí

Cortesía de este artículo .

Eric Smith
fuente
3
Buen articulo. Una imagen vale mas que mil palabras.
AlexWei
Eres una estrella.
mikemay
14

De la documentación de MSDN :

La utilidad real de la propiedad Captures ocurre cuando se aplica un cuantificador a un grupo de captura para que el grupo capture múltiples subcadenas en una sola expresión regular. En este caso, el objeto Grupo contiene información sobre la última subcadena capturada, mientras que la propiedad Capturas contiene información sobre todas las subcadenas capturadas por el grupo. En el siguiente ejemplo, la expresión regular \ b (\ w + \ s *) +. coincide con una oración completa que termina en un punto. El grupo (\ w + \ s *) + captura las palabras individuales en la colección. Debido a que la colección Group contiene información solo sobre la última subcadena capturada, captura la última palabra de la oración, "oración". Sin embargo, cada palabra capturada por el grupo está disponible en la colección devuelta por la propiedad Capturas.

pmarflee
fuente
4

Imagine que tiene la siguiente entrada de texto dogcatcatcaty un patrón comodog(cat(catcat))

En este caso, tiene 3 grupos, el primero ( grupo principal ) corresponde a la coincidencia.

Match == dogcatcatcaty Group0 ==dogcatcatcat

Grupo1 == catcatcat

Grupo2 == catcat

Entonces, ¿de qué se trata?

Consideremos un pequeño ejemplo escrito en C # (.NET) usando la Regexclase.

int matchIndex = 0;
int groupIndex = 0;
int captureIndex = 0;

foreach (Match match in Regex.Matches(
        "dogcatabcdefghidogcatkjlmnopqr", // input
        @"(dog(cat(...)(...)(...)))") // pattern
)
{
    Console.Out.WriteLine($"match{matchIndex++} = {match}");

    foreach (Group @group in match.Groups)
    {
        Console.Out.WriteLine($"\tgroup{groupIndex++} = {@group}");

        foreach (Capture capture in @group.Captures)
        {
            Console.Out.WriteLine($"\t\tcapture{captureIndex++} = {capture}");
        }

        captureIndex = 0;
    }

    groupIndex = 0;
    Console.Out.WriteLine();
        }

Salida :

match0 = dogcatabcdefghi
    group0 = dogcatabcdefghi
        capture0 = dogcatabcdefghi
    group1 = dogcatabcdefghi
        capture0 = dogcatabcdefghi
    group2 = catabcdefghi
        capture0 = catabcdefghi
    group3 = abc
        capture0 = abc
    group4 = def
        capture0 = def
    group5 = ghi
        capture0 = ghi

match1 = dogcatkjlmnopqr
    group0 = dogcatkjlmnopqr
        capture0 = dogcatkjlmnopqr
    group1 = dogcatkjlmnopqr
        capture0 = dogcatkjlmnopqr
    group2 = catkjlmnopqr
        capture0 = catkjlmnopqr
    group3 = kjl
        capture0 = kjl
    group4 = mno
        capture0 = mno
    group5 = pqr
        capture0 = pqr

Analicemos solo el primer partido ( match0).

Como se puede ver hay tres grupos de menor importancia : group3, group4ygroup5

    group3 = kjl
        capture0 = kjl
    group4 = mno
        capture0 = mno
    group5 = pqr
        capture0 = pqr

Esos grupos (3-5) se crearon debido al ' subpatrón ' (...)(...)(...)del patrón principal (dog(cat(...)(...)(...)))

El valor de group3corresponde a su captura ( capture0). (Como en el caso de group4y group5). Eso es porque no hay repetición grupal como (...){3}.


Ok, consideremos otro ejemplo donde hay una repetición grupal .

En caso de modificar el patrón de expresión regular para buscar coincidencias (por código que se muestra más arriba) a partir (dog(cat(...)(...)(...)))de (dog(cat(...){3})), usted notará que existe la siguiente repetición del grupo : (...){3}.

Ahora la salida ha cambiado:

match0 = dogcatabcdefghi
    group0 = dogcatabcdefghi
        capture0 = dogcatabcdefghi
    group1 = dogcatabcdefghi
        capture0 = dogcatabcdefghi
    group2 = catabcdefghi
        capture0 = catabcdefghi
    group3 = ghi
        capture0 = abc
        capture1 = def
        capture2 = ghi

match1 = dogcatkjlmnopqr
    group0 = dogcatkjlmnopqr
        capture0 = dogcatkjlmnopqr
    group1 = dogcatkjlmnopqr
        capture0 = dogcatkjlmnopqr
    group2 = catkjlmnopqr
        capture0 = catkjlmnopqr
    group3 = pqr
        capture0 = kjl
        capture1 = mno
        capture2 = pqr

Nuevamente, analicemos solo el primer partido ( match0).

No hay mas grupos menores group4 y group5debido a la (...){3} repetición ( {n} en donde n> = 2 ) se han fusionado en un solo grupo group3.

En este caso, el group3valor corresponde a él capture2( la última captura , en otras palabras).

Así, si usted necesita todas las 3 capturas interiores ( capture0, capture1, capture2) que tendrá que pasar por el grupo de Capturescolección.

La conclusión es: preste atención a la forma en que diseña los grupos de sus patrones. Usted debe pensar por adelantado qué comportamiento hace que la especificación del grupo, al igual que (...)(...), (...){2}o (.{3}){2}etc.


Con suerte, ayudará a arrojar algo de luz sobre las diferencias entre las Capturas , Grupos y Partidos también.

AlexMelw
fuente