¿Qué son los grupos de equilibrio de expresiones regulares?

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 stack. 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 stack.

    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

     (?\w+)\W+(?\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 stack. 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 stack 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 stack . 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 stack si la subexpresión ... coincide. Entonces, si cambiamos nuestra expresión anterior a

     (?\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 syntax negativa: si la stack 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 stack y realizamos una captura por cada paréntesis de cierre. Si encontramos demasiados paréntesis de cierre, intentará abrir una stack vacía y provocar que el patrón falle:

     ^(?:[^()]|(?[(])|(?< -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 stack. La tercera alternativa coincide ) s mientras se sacan elementos de la stack (¡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 syntax 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 stack 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 syntax 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 stack 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 stack), 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)(?!))$ 

    Tenga en cuenta que esta syntax 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 syntax (?...) ?

    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 stack 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 (?subPattern) , no solo se saca una captura de la stack B , sino también todo lo que se encuentra entre esa captura reventada de B y este grupo actual se empuja a la stack A Entonces, si usamos un grupo como este para el paréntesis de cierre, mientras sacamos los niveles de anidación de nuestra stack, también podemos insertar el contenido del par en otra stack:

     ^(?:[^()]|(?[(])|(?[)]))*(?(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:

    Solo una pequeña adición a la excelente respuesta de M. Buettner:

    ¿Cuál es el problema con la syntax (?) ?

    (?x) es sutilmente diferente de (?< -A>(?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)(?!)) 

    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 stack B está vacía. El arduo trabajo que el motor hizo por nosotros se fue.
    ( ejemplo en Regex Storm )

    (?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)(?!)) 

    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: (?).(.(?).) 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).

    (?) 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.