c# - online - ¿Cuál es la diferencia entre "grupos" y "capturas" en expresiones regulares de.NET?
regex email c# (5)
De la documentation MSDN:
La verdadera utilidad de la propiedad Captures ocurre cuando un cuantificador se aplica a un grupo de captura, de modo que el grupo captura múltiples subcadenas en una única 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. Como la colección de grupo 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.
Estoy un poco confuso sobre cuál es la diferencia entre un "grupo" y una "captura" cuando se trata del lenguaje de expresiones regulares de .NET. Considere el siguiente código de C #:
MatchCollection matches = Regex.Matches("{Q}", @"^/{([A-Z])/}$");
Espero que esto resulte en una sola captura para la letra ''Q'', pero si MatchCollection
las propiedades del MatchCollection
devuelto, 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].Captures
incluyen la captura de la letra "Q"?
Esto se puede explicar con un simple ejemplo (e imágenes).
Coincidencia a las 3:10pm
con la expresión regular ((/d)+):((/d)+)(am|pm)
, y usando el csharp
interactivo Mono:
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" }
Dado que hay múltiples dígitos que coinciden en el cuarto grupo, solo "conseguimos" el último partido si hacemos referencia al grupo (con un ToString()
implícito ToString()
, eso es). Para exponer las coincidencias intermedias, necesitamos profundizar y hacer referencia a la propiedad de Captures
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" }
Cortesía de este artículo .
Imagine que tiene el siguiente texto de entrada dogcatcatcat
y un patrón como dog(cat(catcat))
En este caso, tiene 3 grupos, el primero ( grupo principal ) corresponde al partido.
Coincidencia == dogcatcatcat
y Group0 == dogcatcatcat
catcatcat
== catcatcat
Group2 == catcat
Entonces, ¿de qué se trata?
Consideremos un pequeño ejemplo escrito en C # (.NET) usando la clase Regex
.
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 la primera coincidencia ( match0
).
Como puede ver, hay tres grupos menores : group3
, group4
y group5
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 del group3
corresponde a su captura ( capture0
). (Como en el caso de group4
y group5
). Eso es porque no hay repetición grupal como (...){3}
.
Bien, consideremos otro ejemplo donde hay una repetición grupal .
Si modificamos el patrón de expresión regular que se emparejará (para el código mostrado arriba) de (dog(cat(...)(...)(...)))
a (dog(cat(...){3}))
, notará que hay la siguiente repetición de 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
De nuevo, analicemos solo la primera coincidencia ( match0
).
No hay más grupos menores group4
y group5
debido a (...){3}
repetición ( {n} donde n> = 2 ) se han fusionado en un solo grupo group3
.
En este caso, el valor de group3
corresponde a su capture2
( la última captura , en otras palabras).
Por lo tanto, si necesita las 3 capturas internas ( capture0
, capture1
, capture2
), tendrá que capture2
por la colección de Captures
del grupo.
La conclusión es: preste atención a la forma en que diseña los grupos de su patrón. Debería pensar por adelantado qué comportamiento causa la especificación del grupo, como (...)(...)
, (...){2}
o (.{3}){2}
etc.
Esperemos que ayude a arrojar algo de luz sobre las diferencias entre Capturas , Grupos y Partidos también.
No serás el primero en ser confuso al respecto. Esto es lo que el famoso Jeffrey Friedl tiene que decir al respecto (páginas 437+):
Dependiendo de su punto de vista, agrega una nueva dimensión interesante a los resultados de los partidos, o agrega confusión e hinchazón.
Y más adelante:
La diferencia principal 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 que coincide con el grupo.
Y unas pocas páginas más tarde, esta es su conclusión:
Después de superar la documentación de .NET y comprender realmente qué es lo que estos objetos agregan, 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 usará en la mayoría de los casos.
En otras palabras: son muy similares, pero de vez en cuando y, como sucede, encontrarás un uso para ellos. Antes de que cultives 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. Piensa en Captures como una especie de rastreador de historia. Cuando la expresión regular hace su coincidencia, recorre la cadena de izquierda a derecha (ignorando el retroceso por un momento) y cuando encuentra un paréntesis de captura coincidente, almacenará eso en $x
(x es cualquier dígito), digamos $1
.
Los motores de expresiones regulares normales, cuando se repiten los paréntesis de captura, tirarán el $1
actual y lo reemplazarán con el nuevo valor. Not .NET, que mantendrá este historial y lo ubicará en Captures[0]
.
Si cambiamos su expresión regular para que se vea de la siguiente manera:
MatchCollection matches = Regex.Matches("{Q}{R}{S}", @"(/{[A-Z]/})+");
observará que el primer Group
tendrá una Captures
(el primer grupo siempre será la coincidencia completa, es decir, igual a $0
) y el segundo grupo tendrá {S}
, es decir, solo el último grupo coincidente. Sin embargo, y aquí está el truco, si quieres 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 de las capturas individuales que están claramente allí en la cadena, debe usar Captures
.
Una última palabra sobre su última pregunta: la coincidencia total siempre tiene una Captura total, no la mezcle con los Grupos individuales. Las capturas solo son interesantes dentro de los grupos .
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 están representados 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, antes de analizar esto, habría pensado que Captures sería una selección de las capturas ordenadas por el grupo al que pertenecen. Más bien es solo un alias para los grupos [0]. Capturas. Bastante inútil ..