Consejos para Regex Golf


43

Similar a nuestros hilos para consejos de golf específicos del idioma: ¿cuáles son los trucos generales para acortar las expresiones regulares?

Puedo ver tres usos de regex cuando se trata de jugar al golf: el clásico regex golf ("aquí hay una lista que debería coincidir, y aquí hay una lista que debería fallar"), usando regex para resolver problemas computacionales y expresiones regulares usadas como partes de Código de golf más grande. Siéntase libre de publicar consejos que aborden cualquiera o todos estos. Si su punta está limitada a uno o más sabores, indique estos sabores en la parte superior.

Como de costumbre, siga una sugerencia (o una familia de sugerencias muy relacionadas) por respuesta, de modo que las sugerencias más útiles puedan llegar a la cima mediante la votación.


Auto-promoción flagrante: ¿en qué categoría de uso de expresiones regulares se incluye esto? codegolf.stackexchange.com/a/37685/8048
Kyle Strand

@KyleStrand "expresiones regulares utilizadas como partes de un código de golf más grande".
Martin Ender

Respuestas:


24

Cuando no escapar

Estas reglas se aplican a la mayoría de los sabores, si no a todos:

  • ] no necesita escapar cuando no tiene comparación.

  • {y }no necesitan escapar cuando no son parte de una repetición, por ejemplo, {a}coincide {a}literalmente. Incluso si desea hacer coincidir algo como {2}, solo necesita escapar de uno de ellos, por ejemplo {2\}.

En clases de personajes:

  • ]no necesita escapar cuando es el primer carácter de un conjunto de caracteres, por ejemplo, []abc]coincide con uno de ]abc, o cuando es el segundo carácter después de un ^, por ejemplo, [^]]coincide con cualquier cosa menos ]. (Excepción notable: ¡sabor ECMAScript!)

  • [no necesita escapar en absoluto. Junto con el consejo anterior, esto significa que puedes combinar ambos corchetes con la clase de personaje terriblemente contra-intuitiva [][].

  • ^no necesita escapar cuando es no el primer carácter de un conjunto de caracteres, por ejemplo [ab^c].

  • -no necesita escapar cuando es el primer (segundo después de a ^) o el último carácter en un conjunto de caracteres, por ejemplo [-abc], [^-abc]o [abc-].

  • No es necesario que otros caracteres escapen dentro de una clase de caracteres, incluso si son metacaracteres fuera de las clases de caracteres (excepto la barra diagonal inversa \).

Además, en algunos sabores ^y $se combinan literalmente cuando no están al principio o al final de la expresión regular respectivamente.

(Gracias a @ MartinBüttner por completar algunos detalles)


Algunos prefieren escapar del punto real encerrándolo en una clase de caracteres donde no necesita escapar (por ejemplo [.]). Escaparlo normalmente ahorraría 1 byte en este caso\.
CSᵠ

Tenga en cuenta que se [debe escapar en Java. Sin embargo, no estoy seguro sobre ICU (utilizado en Android e iOS) o .NET.
n̴̖̋h̷͉̃a̷̭̿h̸̡̅ẗ̵̨́d̷̰̀ĥ̷̳

18

Una expresión regular simple para unir todos los caracteres imprimibles en la tabla ASCII .

[ -~]

1
pura genialidad, todos los caracteres de un teclado estándar de EE. UU. nota: la tabla ascii estándar (sin incluir el rango extendido 127-255
CSᵠ

Lo uso a menudo, pero le falta un carácter común "regular": TAB. Y supone que está utilizando LC_ALL = "C" (o similar) ya que algunas otras configuraciones regionales fallarán.
Olivier Dulac

¿Se puede usar el guión así para especificar cualquier rango de caracteres en la tabla ASCII? ¿Funciona para todos los sabores de expresiones regulares?
Josh Withee

14

Conoce tus sabores regex

Hay una cantidad sorprendente de personas que piensan que las expresiones regulares son esencialmente agnósticas al lenguaje. Sin embargo, en realidad hay diferencias bastante sustanciales entre los sabores, y especialmente para el golf de código es bueno conocer algunos de ellos y sus características interesantes, para que pueda elegir el mejor para cada tarea. Aquí hay una descripción general de varios sabores importantes y lo que los distingue de los demás. (Esta lista no puede estar realmente completa, pero avíseme si me perdí algo realmente deslumbrante).

Perl y PCRE

Los estoy tirando en una sola olla, ya que no estoy demasiado familiarizado con el sabor de Perl y son en su mayoría equivalentes (PCRE es para expresiones regulares compatibles con Perl después de todo). La principal ventaja del sabor Perl es que en realidad se puede llamar al código Perl desde el interior de la expresión regular y la sustitución.

  • Recursión / subrutinas . Probablemente la característica más importante para el golf (que solo existe en un par de sabores).
  • Patrones condicionales (?(group)yes|no).
  • Apoya el cambio de caso en la cadena de reemplazo con \l, \u, \Ly \U.
  • PCRE permite alternar en retrospectivas, donde cada alternativa puede tener una longitud diferente (pero fija). (La mayoría de los sabores, incluido Perl, requieren mirar hacia atrás para tener una longitud fija total).
  • \G para anclar una partida al final de la partida anterior.
  • \K para reiniciar el comienzo del partido
  • PCRE admite propiedades de caracteres Unicode y scripts .
  • \Q...\Epara escapar de series más largas de personajes. Es útil cuando intenta hacer coincidir una cadena que contiene muchos metacaracteres.

.RED

Este es probablemente el sabor más poderoso, con muy pocas deficiencias.

Una deficiencia importante en términos de golf es que no admite cuantificadores posesivos como algunos otros sabores. En lugar de .?+tendrás que escribir (?>.?).

Java

  • Debido a un error (consulte el Apéndice), Java admite un tipo limitado de mirar hacia atrás de longitud variable: puede mirar hacia atrás hasta el comienzo de la cadena .*desde donde ahora puede comenzar una búsqueda hacia adelante, como (?<=(?=lookahead).*).
  • Soporta unión e intersección de clases de caracteres.
  • Tiene el soporte más extenso para Unicode, con clases de caracteres para "scripts, bloques, categorías y propiedades binarias Unicode" .
  • \Q...\E como en Perl / PCRE.

Rubí

En versiones recientes, este sabor es igualmente poderoso que PCRE, incluido el soporte para llamadas de subrutina. Al igual que Java, también admite la unión e intersección de clases de caracteres. Una característica especial es la clase de caracteres incorporada para dígitos hexadecimales: \h(y el negado \H).

Sin embargo, la característica más útil para el golf es cómo Ruby maneja los cuantificadores. En particular, es posible anidar cuantificadores sin paréntesis. .{5,7}+funciona y también lo hace .{3}?. Además, a diferencia de la mayoría de los otros sabores, si 0se puede omitir el límite inferior de un cuantificador , por ejemplo, .{,5}es equivalente a .{0,5}.

En cuanto a las subrutinas, la principal diferencia entre las subrutinas de PCRE y las subrutinas de Ruby es que la sintaxis de Ruby es un byte más larga (?n)vs \g<n>, pero las subrutinas de Ruby se pueden usar para capturar, mientras que PCRE restablece las capturas después de que finaliza una subrutina.

Finalmente, Ruby tiene una semántica diferente para los modificadores relacionados con la línea que la mayoría de los otros sabores. El modificador que generalmente se llama men otros sabores siempre está activado en Ruby. Así ^, y $siempre que coincida con el comienzo y el final de una línea no sólo el principio y el final de la cadena. Esto puede ahorrarle un byte si necesita este comportamiento, pero le costará bytes adicionales si no lo hace, ya que tendrá que reemplazar ^y $con \Ay \z, respectivamente. Además de eso, el modificador que generalmente se llama s(que hace que los .avances de línea coincidan) se llama men Ruby. Esto no afecta el recuento de bytes, pero debe tenerse en cuenta para evitar confusiones.

Pitón

Python tiene un sabor sólido, pero no conozco ninguna característica particularmente útil que no encontrarías en ningún otro lado.

Sin embargo , hay un sabor alternativo que está destinado a reemplazar el remódulo en algún momento y que contiene muchas características interesantes. Además de agregar compatibilidad para operadores de combinación de recursión, lookbehinds de longitud variable y clase de caracteres, también tiene la característica única de coincidencia difusa . En esencia, puede especificar una serie de errores (inserciones, eliminaciones, sustituciones) que están permitidos, y el motor también le dará coincidencias aproximadas.

ECMAScript

El sabor ECMAScript es muy limitado y, por lo tanto, rara vez es muy útil para el golf. Lo único que tiene que hacer es que la clase de caracteres vacía negada [^] coincida con cualquier carácter, así como la clase de caracteres vacía que falla incondicionalmente [](a diferencia de lo habitual (?!)). Desafortunadamente, el sabor no tiene ninguna característica que lo haga útil para problemas normales.

Lua

Lua tiene su propio sabor bastante único, que es bastante limitado (por ejemplo, ni siquiera puede cuantificar grupos) pero viene con un puñado de características útiles e interesantes.

  • Tiene una gran cantidad de shorthands para las clases de caracteres incorporadas, incluidos los signos de puntuación, mayúsculas y minúsculas y dígitos hexadecimales.
  • Con %bél admite una sintaxis muy compacta para que coincida con cadenas equilibradas. Por ejemplo, %b()coincide con ay (luego todo hasta una coincidencia )(omitiendo correctamente los pares coincidentes internos). (y )pueden ser dos caracteres cualquiera aquí.

Aumentar

El sabor regex de Boost es esencialmente el de Perl. Sin embargo, tiene algunas características nuevas y agradables para la sustitución de expresiones regulares, que incluyen cambios de casos y condicionales . Este último es exclusivo de Boost, que yo sepa.


Tenga en cuenta que mirar hacia atrás en mirar hacia atrás atravesará el límite límite en la vista hacia atrás. Probado en Java y PCRE.
n̴̖̋h̷͉̃a̷̭̿h̸̡̅ẗ̵̨́d̷̰̀ĥ̷̳

¿No es .?+equivalente a .*?
CalculatorFeline

@CalculatorFeline El primero es un cuantificador posesivo 0 o 1 (en sabores que admiten cuantificadores posesivos), el último es un cuantificador 0 o más.
Martin Ender

@CalculatorFeline ah entiendo la confusión. Hubo un error tipográfico.
Martin Ender

13

Conoce tus clases de personajes

La mayoría de los sabores regex tienen clases de caracteres predefinidas. Por ejemplo, \dcoincide con un dígito decimal, que es tres bytes más corto que [0-9]. Sí, pueden ser ligeramente diferentes, ya \dque también pueden coincidir con los dígitos Unicode en algunos sabores, pero para la mayoría de los desafíos esto no hará la diferencia.

Aquí hay algunas clases de caracteres que se encuentran en la mayoría de los sabores regex:

\d      Match a decimal digit character
\s      Match a whitespace character
\w      Match a word character (typically [a-zA-Z0-9_])

Además, también tenemos:

\D \S \W

que son versiones negadas de lo anterior.

Asegúrese de verificar su sabor para cualquier clase de personaje adicional que pueda tener. Por ejemplo, PCRE tiene \Rpara nuevas líneas y Lua incluso tiene clases como minúsculas y mayúsculas.

(Gracias a @HamZa y @ MartinBüttner por señalarlos)


3
\Rpara nuevas líneas en PCRE.
HamZa

12

No te molestes con los grupos que no capturan (a menos que ...)

Este consejo se aplica a (al menos) todos los sabores populares inspirados en Perl.

Esto puede ser obvio, pero (cuando no se juega al golf) es una buena práctica usar grupos (?:...)que no capturan siempre que sea ​​posible. Sin ?:embargo, estos dos personajes adicionales son un desperdicio cuando juegas al golf, así que solo usa grupos de captura, incluso si no vas a hacer referencia a ellos.

Sin embargo, hay una excepción (rara): si sucede que el grupo de referencia retrocede 10al menos 3 veces, en realidad puede guardar bytes convirtiendo un grupo anterior en un grupo sin captura, de modo que todos esos \10s se conviertan en \9s. (Se aplican trucos similares, si usa el grupo 11al menos 5 veces, etc.)


¿Por qué 11 necesita 5 veces para que valga la pena cuando 10 requiere 3?
Nic Hartley

1
@QPaysTaxes puede usar en $9lugar de $10o $11una vez guarda un byte. Volviendo $10a $9requiere una ?:, que es de dos bytes, por lo que tendrá tres $10s para salvar algo. Volviendo $11a $9requiere dos ?:s que es cuatro bytes, por lo que tendrá cinco $11s para ahorrar algo (o cinco $10y $11combinado).
Martin Ender

10

Recursión por reutilización de patrones

Un puñado de sabores respaldan la recursividad ( que yo sepa , Perl, PCRE y Ruby). Incluso cuando no intente resolver problemas recursivos, esta función puede ahorrar muchos bytes en patrones más complicados. No es necesario hacer la llamada a otro grupo (con nombre o numerado) dentro de ese grupo. Si tiene un cierto patrón que aparece varias veces en su expresión regular, simplemente agrúpelo y consúltelo fuera de ese grupo. Esto no es diferente de una llamada de subrutina en lenguajes de programación normales. Entonces en lugar de

...someComplexPatternHere...someComplexPatternHere...someComplexPatternHere... 

en Perl / PCRE puedes hacer:

...(someComplexPatternHere)...(?1)...(?1)...

o en Ruby:

...(someComplexPatternHere)...\g<1>...\g<1>...

siempre que sea el primer grupo (por supuesto, puede usar cualquier número en la llamada recursiva).

Tenga en cuenta que esto no es lo mismo que una referencia inversa ( \1). Las referencias posteriores coinciden exactamente con la misma cadena que el grupo coincidió la última vez. Estas llamadas de subrutina en realidad evalúan el patrón nuevamente. Como ejemplo para someComplexPatternHeretomar una clase de caracteres larga:

a[0_B!$]b[0_B!$]c[0_B!$]d

Esto coincidiría con algo como

aBb0c!d

Tenga en cuenta que no puede usar referencias anteriores aquí mientras se preserva el comportamiento. Una referencia inversa fallaría en la cadena anterior, porque By 0y !no son lo mismo. Sin embargo, con las llamadas de subrutina, el patrón se vuelve a evaluar. El patrón anterior es completamente equivalente a

a([0_B!$])b(?1)c(?1)d

Captura en llamadas de subrutina

Una nota de precaución para Perl y PCRE: si el grupo 1en los ejemplos anteriores contiene más grupos, entonces las llamadas de subrutina no recordarán sus capturas. Considere este ejemplo:

(\w(\d):)\2 (?1)\2 (?1)\2

Esto no coincidirá

x1:1 y2:2 z3:3

porque después de que regresan las llamadas de la subrutina, 2se descarta la nueva captura de grupo . En cambio, este patrón coincidiría con esta cadena:

x1:1 y2:1 z3:1

Esto es diferente de Rubí, donde las llamadas a subrutinas hacen conservar sus capturas, así, el texto equivalente de Ruby (\w(\d):)\2 \g<1>\2 \g<1>\2se correspondería con el primero de los ejemplos anteriores.


Puedes usar \1para Javascript. Y PHP también (supongo).
Ismael Miguel

55
@IsmaelMiguel Esto no es una referencia inversa. Esto realmente evalúa el patrón nuevamente. Por ejemplo (..)\1, coincidiría ababpero fallaría abbamientras (..)(?1)que coincidirá con el último. En realidad, es una llamada de subrutina en el sentido de que la expresión se aplica nuevamente, en lugar de coincidir literalmente con lo que coincidió la última vez.
Martin Ender

Wow, no tenía idea! Aprendiendo algo nuevo todos los días
Ismael Miguel

En .NET (u otros sabores sin esta característica):(?=a.b.c)(.[0_B!$]){3}d
jimmy23013

@ user23013 que parece muy específico para este ejemplo en particular. No estoy seguro de que sea aplicable si reutilizo un cierto subpatrón en varias búsquedas.
Martin Ender

9

Hacer que un partido falle

Cuando se usa la expresión regular para resolver problemas computacionales o hacer coincidir lenguajes altamente no regulares, a veces es necesario hacer que una rama del patrón falle independientemente de dónde se encuentre en la cadena. El enfoque ingenuo es utilizar una búsqueda anticipada negativa vacía:

(?!)

El contenido (el patrón vacío) siempre coincide, por lo que la búsqueda anticipada negativa siempre falla. Pero la mayoría de las veces, hay una opción mucho más simple: simplemente use un carácter que sabe que nunca aparecerá en la entrada. Por ejemplo, si sabe que su entrada siempre constará solo de dígitos, simplemente puede usar

!

o cualquier otro carácter que no sea un dígito ni un metadato para causar un error.

Incluso si su entrada podría contener alguna subcadena, existen formas más cortas que (?!). Cualquier sabor que permita que las anclas aparezcan dentro de un patrón en lugar del final, podría usar cualquiera de las siguientes soluciones de 2 caracteres:

a^
$a

Sin embargo, tenga en cuenta que algunos sabores se tratarán ^y $como caracteres literales en estas posiciones, porque obviamente no tienen sentido como anclas.

En el sabor ECMAScript también existe la solución elegante de 2 caracteres

[]

Esta es una clase de caracteres vacía, que intenta asegurarse de que los siguientes caracteres sean uno de los de la clase, pero no hay caracteres en la clase, por lo que esto siempre falla. Tenga en cuenta que esto no funcionará en ningún otro sabor, porque las clases de caracteres generalmente no pueden estar vacías.


8

Optimiza tus quirófanos

Siempre que tenga 3 o más alternativas en su RegEx:

/aliceblue|antiquewhite|aquamarine|azure/

Verifique si hay un comienzo común:

/a(liceblue|ntiquewhite|quamarine|zure)/

¿Y tal vez incluso un final común?

/a(liceblu|ntiquewhit|quamarin|zur)e/

Nota: 3 es solo el comienzo y representaría la misma longitud, 4+ marcaría la diferencia


Pero, ¿qué pasa si no todos tienen un prefijo común? (espacio en blanco solo agregado para mayor claridad)

/aliceblue|antiquewhite|aqua|aquamarine|azure
|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood
|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan/

Agrúpelos, siempre que la regla 3+ tenga sentido:

/a(liceblue|ntiquewhite|qua|quamarine|zure)
|b(eige|isque|lack|lanchedalmond|lue|lueviolet|rown|urlywood)
|c(adetblue|hartreuse|hocolate|oral|ornflowerblue|ornsilk|rimson|yan)/

O incluso generalice si la entropía satisface su caso de uso:

/\w(liceblue|ntiquewhite|qua|quamarine|zure
|eige|isque|lack|lanchedalmond|lue|lueviolet|rown|urlywood
|adetblue|hartreuse|hocolate|oral|ornflowerblue|ornsilk|rimson|yan)/

^ en este caso estamos seguros de que no obtenemos ninguno clueocrown slack Ryan

Esto "según algunas pruebas" también mejora el rendimiento, ya que proporciona un ancla para comenzar.


1
Si el inicio o final común es más largo que un carácter, incluso agrupar dos puede marcar la diferencia. Me gusta aqua|aquamarineaqua(|marine)o aqua(marine)?.
Paŭlo Ebermann

6

Este es bastante simple, pero vale la pena declararlo:

Si te encuentras repitiendo la clase de caracteres, [a-zA-Z]probablemente solo puedas usar [a-z]y agregar el i( modificador de mayúsculas y minúsculas) a tu expresión regular.

Por ejemplo, en Ruby, las siguientes dos expresiones regulares son equivalentes:

/[a-zA-Z]+\d{3}[a-zA-Z]+/
/[a-z]+\d{3}[a-z]/i - 7 bytes más cortos

Para el caso, los otros modificadores también pueden acortar su longitud total. En lugar de hacer esto:

/(.|\n)/

lo que coincide con cualquier carácter (porque de puntos no coincide nueva línea), utilice los s ingle modificador de línea s, lo que hace que las nuevas líneas de concordancia de puntos.

/./s - 3 bytes más cortos


En Ruby, hay un montón de clases de personajes incorporadas para expresiones regulares. Vea esta página y busque "Propiedades del personaje".
Un gran ejemplo es el "símbolo de moneda". Según Wikipedia, hay un montón de posibles símbolos de moneda, y ponerlos en una clase de caracteres sería muy costoso ( [$฿¢₡Ð₫€.....]), mientras que puede hacer coincidir cualquiera de ellos en 6 bytes:\p{Sc}


1
Excepto JavaScript, donde el smodificador no es compatible. :( Pero allí puedes usar el /[^]/truco patentado de JavaScript .
manatwork

Tenga en cuenta que (.|\n)ni siquiera funciona en algunos sabores, porque a .menudo tampoco coincide con otros tipos de separadores de línea. Sin embargo, la forma habitual de hacer esto (sin s) [\s\S]es con los mismos bytes que (.|\n).
Martin Ender

@ MartinBüttner, mi idea era mantenerlo junto con los otros consejos relacionados con el final de línea. Pero si cree que esta respuesta es más sobre modificadores, no tengo objeciones si la vuelve a publicar.
manatwork

@manatwork hecho (y también agregó un truco relacionado no relacionado con ES)
Martin Ender

6

Un analizador de lenguaje simple

Puede construir un analizador muy simple con un RE como \d+|\w+|".*?"|\n|\S. Los tokens que necesitas hacer coincidir se separan con el carácter RE 'o'.

Cada vez que el motor RE intenta hacer coincidir en la posición actual en el texto, intentará el primer patrón, luego el segundo, etc. Si falla (en un carácter de espacio aquí, por ejemplo), continúa y vuelve a intentar las coincidencias . El orden es importante. Si colocamos el \Stérmino antes del \d+término, el \Sprimero coincidiría con cualquier carácter no espacial que rompería nuestro analizador.

El ".*?"emparejador de cadenas utiliza un modificador no codicioso, por lo que solo hacemos coincidir una cadena a la vez. Si su RE no tiene funciones no codiciosas, puede usar "[^"]*"cuál es equivalente.

Ejemplo de Python:

text = 'd="dogfinder"\nx=sum(ord(c)*872 for c in "fish"+d[3:])'
pat = r'\d+|\w+|".*?"|\n|\S'
print re.findall(pat, text)

['d', '=', '"dogfinder"', '\n', 'x', '=', 'sum', '(', 'ord', '(', 'c', ')',
    '*', '872', 'for', 'c', 'in', '"fish"', '+', 'd', '[', '3', ':', ']', ')']

Golfed Python Ejemplo:

# assume we have language text in A, and a token processing function P
map(P,findall(r'\d+|\w+|".*?"|\n|\S',A))

Puede ajustar los patrones y su orden para el idioma que necesita hacer coincidir. Esta técnica funciona bien para JSON, HTML básico y expresiones numéricas. Se ha utilizado con éxito muchas veces con Python 2, pero debería ser lo suficientemente general como para funcionar en otros entornos.


6

\K en lugar de mirar hacia atrás positivo

PCRE y Perl admiten la secuencia de escape \K, que restablece el comienzo del partido. Eso ab\Kcdrequerirá que su cadena de entrada contenga, abcdpero la coincidencia informada solo será cd.

Si está utilizando una retrospectiva positiva al comienzo de su patrón (que probablemente sea el lugar más probable), en la mayoría de los casos, puede usar \Ken su lugar y guardar 3 bytes:

(?<=abc)def
abc\Kdef

Esto es equivalente para la mayoría de los propósitos, pero no del todo. Las diferencias traen ventajas y desventajas con ellos:

  • Upside: PCRE y Perl no son compatibles con lookbehind de longitud arbitraria (solo .NET lo hace). Es decir, no puedes hacer algo así (?<=ab*). ¡Pero con \Kusted puede poner cualquier tipo de patrón frente a él! Así ab*\Kfunciona En realidad, esto hace que esta técnica sea mucho más poderosa en los casos en que sea aplicable.
  • Al revés: las miradas no retroceden. Esto es relevante si desea capturar algo en el retrospectivo para referencia posterior más adelante, pero hay varias capturas posibles que conducen a coincidencias válidas. En este caso, el motor regex solo probaría alguna de esas posibilidades. Cuando se usa \Kesa parte de la expresión regular, se retrocede como todo lo demás.
  • Desventaja: Como probablemente sepa, varias coincidencias de una expresión regular no pueden superponerse. A menudo, las búsquedas alternativas se utilizan para evitar esta limitación, ya que la búsqueda anticipada puede validar una parte de la cadena que ya se consumió en una coincidencia anterior. Entonces, si desea hacer coincidir todos los caracteres que siguen ab , puede usar (?<=ab).. Dada la entrada

    ababc
    

    esto coincidiría con el segundo ay el c. Esto no se puede reproducir con \K. Si lo usó ab\K., solo obtendría la primera coincidencia, porque ahora abno está en una búsqueda.


Si un patrón usa la \Ksecuencia de escape dentro de una afirmación positiva, el inicio informado de una coincidencia exitosa puede ser mayor que el final de la coincidencia.
hwnd

@hwnd Mi punto es que ababc, dado , no hay forma de igualar tanto el segundo acomo el ccon \K. Solo obtendrás una coincidencia.
Martin Ender

Estás en lo correcto, no con la función en sí. Tendrías que fondear con\G
hwnd

@hwnd Ah, ahora veo tu punto. Pero supongo que en ese punto (desde una perspectiva de golf) es mejor que tengas una mirada negativa hacia atrás, ya que incluso podrías necesitarlo de todos modos, ya que no puedes estar seguro de que el .del último partido fuera realmente un a.
Martin Ender

1
Uso interesante de \ K =)
hwnd

5

Emparejar cualquier personaje

El sabor ECMAScript carece de smodificadores, lo que hace que .coincida con cualquier carácter (incluidas las nuevas líneas). Esto significa que no hay una solución de un solo carácter para hacer coincidir caracteres completamente arbitrarios. La solución estándar en otros sabores (cuando uno no quiere usar spor alguna razón) es [\s\S]. Sin embargo, ECMAScript es el único sabor (que yo sepa) que apoya las clases de caracteres vacíos, y por lo tanto tiene una alternativa mucho más corto: [^]. Esta es una clase de caracteres vacía negada, es decir, coincide con cualquier carácter.

Incluso para otros sabores, podemos aprender de esta técnica: si no queremos usar s(por ejemplo, porque todavía necesitamos el significado habitual .en otros lugares), todavía puede haber una forma más corta de combinar los caracteres imprimibles y de nueva línea, siempre que haya algún carácter que sepamos que no aparece en la entrada. Digamos que estamos procesando números delimitados por nuevas líneas. Entonces podemos unir cualquier carácter [^!], ya que sabemos que !nunca será parte de la cadena. Esto ahorra dos bytes sobre el ingenuo [\s\S]o [\d\n].


44
En Perl, \Nsignifica exactamente lo que .significa fuera del /smodo, excepto que no se ve afectado por un modo.
Konrad Borowski

4

Utilice grupos atómicos y cuantificadores posesivos.

He encontrado grupos atómicos ( (?>...)) y cuantificadores posesivos ( ?+, *+, ++, {m,n}+) a veces muy útiles para jugar al golf. Coincide con una cadena y no permite retroceder más tarde. Por lo tanto, solo coincidirá con la primera cadena compatible que encuentre el motor regex.

Por ejemplo: para hacer coincidir una cadena con un número impar de a's al principio, que no es seguida por más a' s, puede usar:

^(aa)*+a
^(?>(aa)*)a

Esto le permite usar cosas como .*libremente, y si hay una coincidencia obvia, no habrá otra posibilidad que combine demasiados caracteres o muy pocos, lo que puede romper su patrón.

En .NET regex (que no tiene cuantificadores posesivos), puede usar esto para hacer estallar el grupo 1 el mayor múltiplo de 3 (con un máximo de 30) veces (no se juega muy bien):

(?>((?<-1>){3}|){10})

1
Al ECMAscript también le faltan cuantificadores posesivos o grupos atómicos :(
CSᵠ

4

Olvídese de un grupo capturado después de una subexpresión (PCRE)

Para esta expresión regular:

^((a)(?=\2))(?!\2)

Si desea borrar el \ 2 después del grupo 1, puede usar la recursividad:

^((a)(?=\2)){0}(?1)(?!\2)

Coincidirá aamientras que el anterior no. A veces también puede usar ??o incluso ?en lugar de {0}.

Esto puede ser útil si usa mucho las recursiones, y algunas de las referencias o grupos condicionales aparecieron en diferentes lugares en su expresión regular.

También tenga en cuenta que se suponen grupos atómicos para las recursiones en PCRE. Entonces esto no coincidirá con una sola letra a:

^(a?){0}(?1)a

No lo probé en otros sabores todavía.

Para lookaheads, también puede usar negativos dobles para este propósito:

^(?!(?!(a)(?=\1))).(?!\1)

4

Expresiones opcionales

A veces es útil recordar que

(abc)?

es casi lo mismo que

(abc|)

Sin embargo, hay una pequeña diferencia: en el primer caso, el grupo captura abco no captura en absoluto. El último caso haría que una referencia inversa fallara incondicionalmente. En la segunda expresión, el grupo capturará abco una cadena vacía, donde el último caso haría una coincidencia de referencia incondicional. Para emular este último comportamiento ?, necesitaría rodear todo en otro grupo que costaría dos bytes:

((abc)?)

La versión que usa |también es útil cuando desea ajustar la expresión en alguna otra forma de grupo de todos modos y no le importa la captura:

(?=(abc)?)
(?=abc|)

(?>(abc)?)
(?>abc|)

Finalmente, este truco también se puede aplicar a ungreedy ?donde guarda un byte incluso en su forma sin procesar (y, en consecuencia, 3 bytes cuando se combina con otras formas de grupos):

(abc)??
(|abc)

1

Múltiples lookaheads que siempre coinciden (.NET)

Si tiene 3 o más construcciones anticipadas que siempre coinciden (para capturar subexpresiones), o hay un cuantificador en una búsqueda anticipada seguido de otra cosa, por lo que deberían estar en un grupo no necesariamente capturado:

(?=a)(?=b)(?=c)
((?=a)b){...}

Estos son más cortos:

(?(?(?(a)b)c))
(?(a)b){...}

donde ano debe ser el nombre de un grupo capturado. No se puede usar |para decir lo habitual dentro by csin agregar otro par de paréntesis.

Desafortunadamente, los grupos de equilibrio en los condicionales parecían tener errores, haciéndolo inútil en muchos casos.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.