Cuantificadores codiciosos vs perezosos en regex

Resumen

Cuando trabajas con expresiones regulares, los cuantificadores codiciosos pueden arruinar tus coincidencias sin que te des cuenta. La buena noticia: la solución cabe en un solo carácter, un signo de interrogación que transforma el comportamiento del motor por completo.

Este es uno de los temas que más fricción genera al aprender regex, así que conviene ir con calma y con el modelo mental del motor siempre presente.

¿Por qué los cuantificadores son codiciosos por defecto?

Un cuantificador es codicioso porque siempre intenta emparejar tantos caracteres como sea posible mientras la regex siga teniendo éxito. Es un bug silencioso: funciona, pero atrapa mucho más texto del que querías.

Tomemos la regex .+, que significa uno o más caracteres de cualquier tipo. El punto representa cualquier carácter y el signo de más representa la repetición. Ese cuantificador se va por el más de la frase y toma todo lo que puede [00:52].

¿Qué es un cuantificador codicioso? Es aquel que empareja la mayor cantidad de caracteres posible. Por defecto todos los cuantificadores en regex son codiciosos, así que consumen el texto hasta el final y luego se devuelven buscando la coincidencia de cierre.

¿Cómo actúa el motor cuando la regex es codiciosa?

Imagina que quieres extraer etiquetas HTML muy básicas con la regex <.+>. Buscas el signo de desigualdad de apertura, cualquier cosa en medio, y el signo de cierre. Suena razonable, pero el resultado es un match gigante que toma toda la cadena [2:30].

En el debugger se ve claro:

  • El motor lee de forma literal el primer símbolo de apertura.
  • Como .+ es codicioso, recorre hasta el final de la cadena.
  • Desde ahí se devuelve buscando el primer símbolo de cierre.
  • Encuentra el último símbolo de la cadena y ahí cierra el match [2:50].

Por eso captura todo el contenido entre la primera apertura y la última cadena de cierre, en lugar de cada etiqueta por separado.

¿Cómo convertir un cuantificador codicioso en perezoso?

Un cuantificador perezoso, también llamado reluctante, hace exactamente lo opuesto: empareja lo mínimo necesario para que el patrón funcione. Lo obliga a detenerse ante la primera oportunidad válida [1:20].

La sintaxis es simple: agrega un signo de interrogación después del cuantificador. Así <.+> se convierte en <.+?> y devuelve los cuatro matches separados que querías desde el inicio [3:20].

¿Cómo hago perezoso un cuantificador en regex? Añade un signo de interrogación justo después del cuantificador. Por ejemplo, .+ se vuelve .+? y en lugar de consumir todo el texto, cierra el match en la primera coincidencia válida.

Con ese signo le dices al cuantificador: avanza, pero cuando veas el primer símbolo de cierre, ahí terminas. Al caminar por el debugger verás que solo toma la letra y luego el símbolo de cierre [3:40].

¿Qué es una clase negada y por qué es más eficiente?

Existe una tercera vía que no depende de la pereza: la clase negada. En lugar de escribir <.+?>, defines una clase que consuma cualquier carácter que no sea el símbolo de cierre.

Dentro de los corchetes colocas el circunflejo para indicar la negación y luego el símbolo que quieres negar, seguido del cuantificador para que ocurra una o más veces [4:20]. La diferencia de rendimiento es real:

  • En la versión perezosa, el motor avanza, revisa lo que hay adelante y luego se devuelve para confirmar y tomar el carácter.
  • En la versión con negación, el motor llega al carácter y se pregunta en el momento si es o no el símbolo de cierre.
  • Si lo es, lo toma; si no, continúa, sin ir y volver.

Por eso resulta más óptima: va y toma de una vez, en lugar de recorrer y regresar [4:50].

¿También son codiciosos los cuantificadores con llaves?

Sí. Los cuantificadores con llave {N,M} también son codiciosos por defecto y se vuelven perezosos con la misma regla del signo de interrogación [5:15].

  • La versión codiciosa {N,M} significa entre N y M, intentando tomar M.
  • La versión perezosa {N,M}? significa entre N y M, intentando tomar N.

Un detalle importante: la codicia y la pereza combinadas con otros patrones pueden generar problemas de rendimiento graves, así que hay que tener cuidado con eso.

¿Cómo aplico esto para extraer texto entre comillas de un log?

Una tarea típica es extraer el contenido entre comillas dobles de un log, incluyendo las comillas. El primer intento codicioso sería ".+", que empareja la comilla inicial, cualquier carácter, y la comilla final [5:50].

El problema aparece rápido. En una línea con dos textos entrecomillados, como search query y año fiscal, la regex codiciosa los une en un solo match porque toma desde la primera comilla hasta la última [6:15].

Las tres estrategias resuelven el mismo caso:

  1. Versión perezosa ".+?", que cierra en la primera comilla que encuentra y devuelve dos matches separados.
  2. Versión con clase negada, donde entre comillas pides todo lo que no sea una comilla.
  3. La clase negada es un poco más eficiente porque evita el ir y volver del motor [7:20].

Con la versión perezosa el motor respeta las fronteras del texto: sabe que a partir de la primera comilla solo debe encontrar una, sin recorrer todo hasta el final y devolverse.

Un recordatorio antes de seguir: perezoso no siempre es mejor ni más rápido. Eso conecta con la optimización del rendimiento que viene más adelante. Y el siguiente paso deja atrás el emparejar caracteres para pasar a emparejar posiciones.

¿Ya probaste convertir alguna de tus regex codiciosas en perezosas? Cuéntame en los comentarios cómo te fue con la clase negada.