c# - probar - ¿Qué son los grupos de equilibrio de expresiones regulares?
expresiones regulares java (2)
Estaba leyendo una pregunta sobre cómo obtener datos dentro de llaves dobles ( esta pregunta ), y luego alguien mencionó los grupos de equilibrio. Todavía no estoy seguro de qué son y cómo usarlos.
Leí a través de Definición de Grupo de Equilibrio , pero la explicación es difícil de seguir, y todavía estoy bastante confundido con las preguntas que mencioné.
¿Podría alguien simplemente explicar qué son los grupos de equilibrio y cómo son útiles?
Por lo que yo sé, los grupos de equilibrio son exclusivos del sabor regex de .NET.
Aparte: grupos repetidos
En primer lugar, debe saber que .NET es (nuevamente, hasta donde yo sé) el único sabor regex que le permite acceder a múltiples capturas de un solo grupo de captura (no en las referencias posteriores sino después de que la coincidencia se haya completado).
Para ilustrar esto con un ejemplo, considere el patrón
(.)+
y la cadena "abcd"
.
en todos los demás sabores de expresiones regulares, capturar el grupo 1
simplemente arrojará un resultado: d
(nota, por supuesto, la coincidencia completa se realizará como se esperaba). Esto se debe a que cada nuevo uso del grupo de captura sobrescribe la captura anterior.
.NET, por otro lado, los recuerda a todos. Y lo hace en una pila. Después de coincidir con la expresión regular anterior como
Match m = new Regex(@"(.)+").Match("abcd");
encontrarás eso
m.Groups[1].Captures
Es una CaptureCollection
cuyos elementos corresponden a las cuatro capturas
0: "a"
1: "b"
2: "c"
3: "d"
donde el número es el índice en CaptureCollection
. Entonces, básicamente, cada vez que se usa el grupo nuevamente, se inserta una nueva captura en la pila.
Se vuelve más interesante si estamos usando grupos de captura nombrados. Como .NET permite el uso repetido del mismo nombre, podríamos escribir una expresión regular como
(?<word>/w+)/W+(?<word>/w+)
para capturar dos palabras en el mismo grupo. De nuevo, cada vez que se encuentra un grupo con un cierto nombre, se inserta una captura en su pila. Entonces aplicando esta expresión regular a la entrada "foo bar"
e inspeccionando
m.Groups["word"].Captures
encontramos dos capturas
0: "foo"
1: "bar"
Esto nos permite incluso empujar cosas en una sola pila desde diferentes partes de la expresión. Pero aún así, esta es solo la función de .NET de poder rastrear múltiples capturas que se enumeran en este CaptureCollection
. Pero dije, esta colección es una pila . Entonces, ¿podemos sacar cosas de ella?
Ingrese: Grupos de equilibrio
Resulta que podemos. Si utilizamos un grupo como (?<-word>...)
, la última captura se saca de la word
pila si la subexpresión ...
coincide. Entonces, si cambiamos nuestra expresión anterior a
(?<word>/w+)/W+(?<-word>/w+)
Luego, el segundo grupo mostrará la captura del primer grupo, y al final recibirá una CaptureCollection
vacía. Por supuesto, este ejemplo es bastante inútil.
Pero hay un detalle más en la sintaxis negativa: si la pila ya está vacía, el grupo falla (independientemente de su subpatrón). Podemos aprovechar este comportamiento para contar los niveles de anidación, y de ahí viene el nombre del grupo de equilibrio (y donde se pone interesante). Digamos que queremos hacer coincidir las cadenas que están entre paréntesis. Empujamos cada paréntesis de apertura en la pila y realizamos una captura por cada paréntesis de cierre. Si encontramos demasiados paréntesis de cierre, intentará abrir una pila vacía y provocar que el patrón falle:
^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*$
Entonces tenemos tres alternativas en una repetición. La primera alternativa consume todo lo que no es paréntesis. La segunda alternativa coincide (
s mientras los empuja en la pila. La tercera alternativa coincide )
s mientras se sacan elementos de la pila (¡si es posible!).
Nota: ¡ Solo para aclarar, solo estamos verificando que no haya paréntesis sin par! Esto significa que la cadena que no contiene paréntesis coincidirá, porque todavía son sintácticamente válidas (en alguna sintaxis donde necesite que coincidan sus paréntesis). Si quiere asegurarse al menos de un par de paréntesis, simplemente agregue un lookahead (?=.*[(])
Justo después de ^
.
Este patrón no es perfecto (o completamente correcto) sin embargo.
Finale: Patrones condicionales
Hay una captura más: esto no asegura que la pila esté vacía al final de la cadena (por lo tanto (foo(bar)
sería válido) .NET (y muchos otros sabores) tienen una construcción más que nos ayuda aquí : patrones condicional. La sintaxis general es
(?(condition)truePattern|falsePattern)
donde el falsePattern
es opcional; si se omite, el falso caso siempre coincidirá. La condición puede ser un patrón o el nombre de un grupo de captura. Me centraré en el último caso aquí. Si es el nombre de un grupo de captura, truePattern
se usa solo si la pila de captura para ese grupo en particular no está vacía. Es decir, un patrón condicional como (?(name)yes|no)
dice "si el name
ha coincidido y ha capturado algo (que aún está en la pila), use el patrón yes
contrario, utilice el patrón no
".
Entonces, al final de nuestro patrón anterior podríamos agregar algo como (?(Open)failPattern)
que hace que todo el patrón falle, si el Open
stack no está vacío. Lo más simple para hacer que el patrón fracase incondicionalmente es (?!)
(Un lookahead negativo vacío). Entonces tenemos nuestro patrón final:
^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*(?(Open)(?!))$
Tenga en cuenta que esta sintaxis condicional no tiene nada que ver con el equilibrio de grupos, pero es necesario aprovechar toda su potencia.
Desde aquí, el cielo es el límite. Muchos usos muy sofisticados son posibles y hay algunos errores cuando se usan en combinación con otras características de .NET-Regex como look-behind de longitud variable ( que tuve que aprender por mi cuenta ). Sin embargo, la pregunta principal siempre es: ¿su código aún puede mantenerse al usar estas características? Debe documentarlo muy bien y asegurarse de que todos los que trabajan en él también conozcan estas características. De lo contrario, es posible que esté mejor, simplemente pase la cuerda de forma manual carácter por carácter y contando los niveles de anidación en un número entero.
Adición: ¿Qué pasa con la sintaxis (?<AB>...)
?
Los créditos para esta parte van a Kobi (ver su respuesta a continuación para más detalles).
Ahora con todo lo anterior, podemos validar que una cadena está entre paréntesis. Pero sería mucho más útil, si pudiéramos obtener capturas (anidadas) para el contenido de todos los paréntesis. Por supuesto, podemos recordar abrir y cerrar paréntesis en una pila de captura separada que no se vacía, y luego hacer una extracción de subcadena basada en sus posiciones en un paso separado.
Pero .NET proporciona una característica de conveniencia más aquí: si usamos (?<AB>subPattern)
, no solo se saca una captura de la pila B
, sino también todo lo que se encuentra entre esa captura reventada de B
y este grupo actual se empuja a la pila A
Entonces, si usamos un grupo como este para el paréntesis de cierre, mientras sacamos los niveles de anidación de nuestra pila, también podemos insertar el contenido del par en otra pila:
^(?:[^()]|(?<Open>[(])|(?<Content-Open>[)]))*(?(Open)(?!))$
Kobi proporcionó esta demostración en vivo en su respuesta
Así que tomando todas estas cosas en conjunto podemos:
- Recuerde arbitrariamente muchas capturas
- Validar estructuras anidadas
- Capture cada nivel de anidación
Todo en una sola expresión regular. Si eso no es emocionante ...;)
Algunos recursos que encontré útiles cuando me enteré de ellos:
- http://blog.stevenlevithan.com/archives/balancing-groups
- MSDN en grupos de equilibrio
- MSDN en patrones condicionales
- http://kobikobi.wordpress.com/tag/balancing-group/ (algo académico, pero tiene algunas aplicaciones interesantes)
Solo una pequeña adición a la excelente respuesta de M. Buettner:
¿Cuál es el problema con la sintaxis (?<AB>)
?
(?<AB>x)
es sutilmente diferente de (?<-A>(?<B>x))
. Dan como resultado el mismo flujo de control * , pero capturan de manera diferente.
Por ejemplo, veamos un patrón para llaves equilibradas:
(?:[^{}]|(?<B>{)|(?<-B>}))+(?(B)(?!))
Al final del partido tenemos una cuerda equilibrada, pero eso es todo lo que tenemos, no sabemos dónde están las llaves porque la pila B
está vacía. El arduo trabajo que el motor hizo por nosotros se fue.
( ejemplo en Regex Storm )
(?<AB>x)
es la solución para ese problema. ¿Cómo? No captura x
en $A
: captura el contenido entre la captura anterior de B
y la posición actual.
Vamos a usarlo en nuestro patrón:
(?:[^{}]|(?<Open>{)|(?<Content-Open>}))+(?(Open)(?!))
Esto capturaría en $Content
las cadenas entre las llaves (y sus posiciones), para cada par en el camino.
Para la cadena {1 2 {3} {4 5 {6}} 7}
, habría cuatro capturas: 3
, 6
, 4 5 {6}
y 1 2 {3} {4 5 {6}} 7
- mucho mejor que nada o }
}
}
}
.
( ejemplo: haga clic en la pestaña de la table
y mire ${Content}
, capturas )
De hecho, se puede usar sin equilibrar: (?<A>).(.(?<Content-A>).)
Captura los primeros dos caracteres, aunque estén separados por grupos.
(una búsqueda anticipada se usa más comúnmente aquí, pero no siempre se escala: puede duplicar su lógica).
(?<AB>)
es una característica importante: le brinda control exacto sobre sus capturas. Téngalo en cuenta cuando intente obtener más de su patrón.