Expresiones regulares en JavaScript (y otros lenguajes): búsquedas anticipadas y retrasadas

07/06/2019Artículo original

Como ya he dicho en muchas otras ocasiones, en mi opinión las expresiones regulares son una de las herramientas más potentes que puede aprender un programador, y no solo para programar ya que sirven también en muchos entornos y herramientas.

Hoy me voy a detener a explicar con detalle el funcionamiento de una característica potente e infrautilizada de las expresiones regulares y que muchos programadores no entienden bien por lo que he podido comprobar. Se trata de las búsquedas anticipadas (lookahead asssertions en inglés) y las búsquedas retrasadas (lookbehind asssertions en inglés), que colectivamente se suelen denominar “Búsquedas alrededor” (del inglés lookaround asssertions).

Vamos a ver en qué consisten y cómo funcionan.

Búsquedas anticipadas

Desde que se presentaron las expresiones regulares en JavaScript hace 20 años, en 1999, se soportaban ya las búsquedas anticipadas tanto positivas como negativas. Vamos a ver en qué consisten.

Búsquedas anticipadas positivas

Una búsqueda anticipada positiva se define mediante el uso de la expresión (?=…), siendo los puntos suspensivos el valor a buscar. Permiten localizar un patrón que vaya justo a continuación de otro, pero solamente captura al primero.

Por ejemplo, la expresión regular /Windows\s(?=7|8|10)/ coincide con el primer “Windows” en la frase “Windows 8 fue muy polémico, más incluso que Windows Vista”, pero no coincide con el de “Windows Vista” del final de la frase. Solamente devuelve “Windows” como resultado de la búsqueda, obviando el “8”. Este es su aspecto visual si la analizamos con RegExper:

Si lo probamos en Regex101 con una frase como la anterior que haga mención a varias versiones de Windows, vemos que solamente captura la palabra “Windows ” (con el espacio incluído) cuando va seguido de 7, 8 o 10, pero no en otros casos, que es justo lo que queremos:

Otro detalle sutil pero importante a tener en cuenta es que estas búsquedas anticipadas no consumen caracteres, es decir, después de que se produce una coincidencia, la búsqueda de la siguiente coincidencia comienza inmediatamente después de la anterior, no después de los caracteres que componen la cadena de búsqueda anticipada. Un ejemplo tonto de esto para entenderlo… Si tenemos la expresión regular /[abcd](?=ab|cd)/ lo que quiere decir es que va a buscar cualquiera de las letras a, b, c o d que vayan seguidas de las dos letras ab o dos letras cd. O sea:

  Programación funcional: Inmutabilidad y funciones puras

Si buscamos con esa expresión regular dentro de en la cadena “aabcd” esto es lo que se encuentra:

Se localizan tanto la primera a como la b. El motivo es el explicado: la parte de la búsqueda anticipada no se cuenta para el avance de la búsqueda, reanudándose ésta a partir de la parte encontrada que vaya delante de los paréntesis. Así, en la primera coincidencia (que son las tres primeras letras aab), se reanuda la búsqueda no después de estas 3 letras, sino después de la primera de ellas (la a) que es la coincidencia que se captura. Por lo tanto puede encontrarse la segunda coincidencia bcd, que de otro modo no se encontraría.

Suena lioso pero no lo es si te paras un poco a pensarlo y lo pruebas con las herramientas que te estoy referenciando.

Búsquedas anticipadas negativas

Las búsqueda anticipadas negativas funcionan de manera idéntica a las anteriores pero a la inversa. Es decir, en lugar de considerar las coincidencias consideran las no-coincidencias.

Se denotan con una expresión como esta: (?!…), o sea, con una admiración final en lugar de un igual, siendo los puntos suspensivos el modelo con el que no queremos coincidir.

Por ejemplo, la expresión regular /Windows\s(?!7|8|10)/ haría justo la búsqueda inversa a la que hicimos antes, evitando las menciones a Windows 7, 8 o 10:

Si ahora la ejecutamos para ver cóm actúa, veremos que localiza el “Windows ” de “Windows Vista”, pero no lo hace con ninguno de los otros:

Al igual que las anteriores, la captura no incluye la expresión dentro del paréntesis, por eso es anticipada, con los mismos efectos que en el caso anterior.

Búsquedas retrasadas

Una de las novedades de ECMAScript 2018 o ES9, presentado en junio de 2018, fue la introducción de este tipo de búsqueda que antes no estaban soportadas.

  Configurar Eclipse/Java para programadores de Visual Studio/C#

Las búsquedas retrasadas son similares a las que acabamos de ver pero se colocan delante del patrón que queremos localizar, no detrás.

Se denotan con una expresión del tipo (?<=...) en el caso de las positivas y (?

Por ejemplo, por poner un ejemplo “geek”, supongamos que queremos localizar en una lista de nombres todos los apellidos de personas que se llamen “Steve”, desechando los apellidos de otras personas de la lista. Esto es muy fácil de conseguir con una expresión de búsqueda retrasada positiva como esta: (?<=Steve )\w+ que se representaría visualmente así:

Y si la ejecutamos en Regex101 con una lista veremos cómo identifica rápidamente todos los apellidos ilustres de los Steve que hemos metido en la lista (en este caso hay 2):

Lo mismo se puede hacer a la inversa con una búsqueda retrasada negativa, de modo que en nuestro ejemplo localice todos los apellidos de la lista de gente que no se llame Steve. Lograr esto es un poco más complicado ya que ahora debemos indicar más condiciones o nos localizará cosas que no son apellidos, ya que si solo ponemos (?

Se busca cualquier fragmento de texto que esté al final de una línea, que sea una palabra entera y que no esté precedida por “Steve “. Otra opción similar habría sido quitar el espacio de “Steve ” y ponerlo en el patrón a buscar, así: (?

  5 herramientas gratuitas indispensables para el desarrollador .NET

Bien, usando esta expresión podemos ver que localiza perfectamente los apellidos de los no llamados Steve:

consiguiendo lo que esperábamos.

En resumen

Las expresiones regulares son una herramienta súper-potente a la que deberíamos aprender a sacar partido a fondo. Si vamos más allá de lo básico pueden llegar a ser muy complejas y bastante crípticas, pero pueden ahorrarnos horas de trabajo en muchas tareas.

En esta ocasión hemos estudiado cómo funcionan las búsquedas adelantadas y retrasadas, de las que no todo el mundo tiene claro su funcionamiento, con algunos ejemplos prácticos. Aunque me he enfocado en JavaScript, en realidad todo lo explicado sirve para casi cualquier otro lenguaje de programación que soporte expresiones regulares, como Java, .NET/C#, Ruby, PHP… así que, como cualquier cosa que aprendas sobre expresiones regulares, te servirá igualmente en cualquier lenguaje o entorno.

¡Espero que te resulte útil!

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Contiene enlaces a sitios web de terceros con políticas de privacidad ajenas que podrás aceptar o no cuando accedas a ellos. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad