2010-01-24 18 views
6

Soy bastante decente con expresiones regulares, y ahora estoy tratando una vez más de entender las afirmaciones de mirar hacia adelante y mirar hacia atrás. En su mayoría tienen sentido, pero no estoy muy seguro de cómo el orden afecta el resultado. He estado mirando this site que coloca lookbehinds antes de la expresión y lookaheads después de la expresión. Mi pregunta es, ¿esto cambia algo? Una respuesta reciente aquí en SO colocó la anticipación antes de la expresión que conduce a mi confusión.Regex lookahead pidiendo

Respuesta

9

Cuando tutoriales introducen lookarounds, tienden a elegir el caso de uso más simple para cada uno. Entonces usarán ejemplos como (?<!a)b ('b' no precedido por 'a') o q(?=u) ('q' seguido de 'u'). Es solo para evitar abarrotar la explicación con detalles que distraen, pero tiende a crear (o reforzar) la impresión de que el aspecto detrás y el aspecto se supone que aparecen en un orden determinado. Me tomó bastante tiempo superar esa idea, y también he visto a otros afligidos por ella.

Intente buscar ejemplos más realistas. Una pregunta que surge mucho es la validación de contraseñas; por ejemplo, asegurándose de que una nueva contraseña tenga al menos seis caracteres y contenga al menos una letra y un dígito. Una manera de hacerlo sería:

^(?=.*[A-Za-z])(?=.*\d)[A-Za-z0-9]{6,}$ 

El carácter de clase [A-Za-z0-9]{6,} podría coincidir con todas las letras o todos los dígitos, por lo que utilizar los símbolos de anticipación para asegurarse de que hay al menos uno de cada uno. En este caso, tiene que hacer el lookaheads primero, porque las partes posteriores de la expresión regular deben poder examinar toda la cadena.

Para otro ejemplo, supongamos que necesita encontrar todas las apariciones de la palabra "allí" a menos que esté precedida por una comilla. La expresión regular obvia para eso es (?<!")[Tt]here\b, pero si está buscando un corpus grande, podría crear un problema de rendimiento. Como está escrito, esa expresión regular hará una mirada negativa detrás de todas y cada una de las posiciones en el texto, y solo cuando eso tenga éxito verificará el resto de la expresión regular.

Cada motor regex tiene sus propias fortalezas y debilidades, pero una cosa que es verdad de todas ellas es que son más rápidas para encontrar secuencias fijas de caracteres literales que cualquier otra cosa: cuanto más larga sea la secuencia, mejor. Esto significa que puede ser mucho más rápido para hacer la búsqueda hacia atrás último, a pesar de que significa adaptar la palabra dos veces:

[Tt]here\b(?<!"[Tt]here) 

Así que la norma que regula la colocación de lookarounds es que no hay una regla; los pones donde más les parezca en cada caso.

1

1(?=ABC) significa - busque 1, y haga coincidir (pero no capture) ABC después de él.
(?<=ABC)1 significa - hacer coincidir (pero no capturar) ABC antes de la ubicación actual, y continuar coincidiendo con 1.
Por lo tanto, normalmente, colocará la búsqueda anticipada después de la expresión y el aspecto detrás de ella.

Cuando colocamos un lookbehind después de la expresión, estamos volviendo a verificar la cadena con la que ya hemos coincidido. Esto es común cuando tiene condiciones complejas (puede pensarlo como AND de expresiones regulares). Por ejemplo, echar un vistazo a esta respuesta reciente de Daniel Brückner:

.&.(?<! &) 

En primer lugar, permite capturar un símbolo de unión entre los dos personajes. A continuación, compruebe que no eran espacios (\S&\S no funcionaría aquí, el OP quería capturar 1&_).

+0

'[^] & [^]' es probablemente más fácil de entender que '. &. (? Gumbo

+2

Eso no coincidirá con "esto y aquello", mientras que la versión de lookbehind lo hará. Un equivalente válido habría sido: '\ S & | & \ S' –

4

Es más fácil mostrarlo en un ejemplo que explicar, creo. Vamos a tomar esta expresión regular:

(?<=\d)(?=(.)\1)(?!p)\w(?<!q) 

Lo que esto significa es:

  1. (?<=\d) - asegúrese de que lo que viene antes de la posición de coincidencia es un dígito.
  2. (?=(.)\1) - asegúrese de que cualquier caracter que coincida en esta (misma) posición sea seguido de una copia de sí mismo (a través de la retro-referencia).
  3. (?!p) - asegúrese de que lo que sigue no sea un p.
  4. \w - una letra, un dígito o un guión bajo. Tenga en cuenta que esta es la primera vez que realmente combinamos y consumimos el personaje.
  5. (?<!q) - asegúrese de que lo que hemos encontrado hasta ahora no termina con un q.

Todo esto coincidirá con cadenas como abc5ddx o 9xx pero no 5d o 6qq o asd6pp o add. Tenga en cuenta que cada afirmación funciona de forma independiente. Simplemente se detiene, mira a su alrededor y, si todo está bien, permite que la coincidencia continúe.

Tenga en cuenta también que en la mayoría (probablemente todas) las implementaciones, lookbehinds tienen la limitación de ser de longitud fija. No puede usar operadores de repetición/opcionalidad como ?, * y + en ellos. Esto se debe a que para que coincida con un patrón, necesitamos un punto de partida; de lo contrario, tendríamos que intentar hacer coincidir cada mirada detrás de cada punto de la cadena.

una muestra de ejecución de esta expresión regular en la cadena a3b5ddx es el siguiente:

  1. posición del cursor de texto: 0.
    1. tratar de coincidir con la primera búsqueda hacia atrás en la posición -1 (ya que siempre coincide con \d 1 personaje). No podemos hacer coincidir los índices negativos, así que falla y avanza el cursor.
  2. posición del cursor de texto: 1.
    1. tratar de coincidir con la primera búsqueda hacia atrás en la posición 0. a no coincide con lo que \d fallar y avanzar el cursor de nuevo.
  3. posición del cursor de texto: 2.
    1. tratar de coincidir con la primera búsqueda hacia atrás en la posición 1. 3 coincide \d a fin de mantener el cursor intacto y continuar haciendo juego.
    2. tratar de coincidir con la primera búsqueda hacia delante en la posición 2. b partidos (.) y es capturado. 5 no coincide con \1 (que es el b capturado). Por lo tanto, fallar y avanzar el cursor.
  4. posición del cursor de texto: 3.
    1. tratar de coincidir con la primera búsqueda hacia atrás en la posición 2. b no coincide con lo que \d fallar y avanzar el cursor de nuevo.
  5. posición del cursor de texto: 4.
    1. tratar de coincidir con la primera búsqueda hacia atrás en la posición 3. 5 coincide \d a fin de mantener el cursor intacto y continuar haciendo juego.
    2. tratar de coincidir con la primera búsqueda hacia delante en la posición 4. d partidos (.) y es capturado. El segundo d coincide con \1 (que es el primer d capturado). Permitir que la coincidencia continúe desde donde lo dejamos.
    3. Intente hacer coincidir el segundo intento. b en la posición 4 no coincide con p, y dado que esto es una búsqueda negativa, eso es lo que queremos; Permita que la coincidencia continúe.
    4. tratar de igualar \w en la posición 4. b partidos. Avance el cursor ya que hemos consumido un personaje y continuamos. También marque esto como el inicio del partido.
  6. posición del cursor de texto: 5.
    1. tratar de igualar la segunda búsqueda hacia atrás en la posición 4 (desde q siempre coincide con 1 carácter). d no coincide con q que es lo que queremos de un lookbehind negativo.
    2. Date cuenta de que estamos al final de la expresión regular e informamos el éxito al devolver la subcadena desde el inicio del partido a la posición actual (4 a 5), ​​que es d.
Cuestiones relacionadas