Regex exactamente n O m veces

105

Considere la siguiente expresión regular, donde Xes cualquier expresión regular.

X{n}|X{m}

Esta expresión regular probaría que Xocurra exactamente n o mveces.

¿Existe un cuantificador de expresiones regulares que pueda probar una ocurrencia Xexactamente no mveces?

FThompson
fuente
No. Dos apariciones de Xes el mejor que se puede obtener de forma en general m, n.
John Dvorak
Si este fuera mi problema, probaría las referencias anteriores de expresiones regulares y comenzaría con (X)\1{n-1}(?:\1{m-n-1}). Sé que esto coincide Xal menos una vez, pero solo para comenzar, pruebe esto simple y luego refine usando lookaheads o lookbehinds en lugar de (X).
finalmente

Respuestas:

91

No existe un cuantificador único que signifique "exactamente m o n veces". La forma en que lo estás haciendo está bien.

Una alternativa es:

X{m}(X{k})?

donde m < ny kes el valor de n-m.

Mark Byers
fuente
67

Aquí está la lista completa de cuantificadores (ref. Http://www.regular-expressions.info/reference.html ):

  • ?, ??- 0 o 1 ocurrencias ( ??es perezoso, ?es codicioso)
  • *, *?- cualquier número de ocurrencias
  • +, +?- al menos una ocurrencia
  • {n}- nocurrencias exactas
  • {n,m}- na mocurrencias, inclusive
  • {n,m}?- na las mocurrencias, perezoso
  • {n,}, {n,}?- al menos nocurrencia

Para obtener "exactamente N o M", debe escribir la expresión regular cuantificada dos veces, a menos que m, n sean especiales:

  • X{n,m} Si m = n+1
  • (?:X{n}){1,2} Si m = 2n
  • ...
John Dvorak
fuente
1
¿Por qué es ?:necesario en el m = 2nejemplo if ? Parece funcionar bien sin él para mí.
erb
7
@erb si lo dejas fuera ?:, el grupo se convierte en un grupo de captura. Aparte de que el motor de expresiones regulares recuerda cosas que no tiene por qué, si tiene grupos de captura después de este, sus ID cambiarán. Si usa su expresión regular para la sustitución, tendrá que ajustar el reemplazo.
John Dvorak
3

TLDR; (?<=[^x]|^)(x{n}|x{m})(?:[^x]|$)

Parece que quieres "xn veces" o "xm veces", creo que una traducción literal a regex sería (x{n}|x{m}). como esta https://regex101.com/r/vH7yL5/1

o, en un caso en el que pueda tener una secuencia de más de m "x" s (suponiendo que m> n), puede agregar 'después de no "x"' y 'seguido de no "x", traduciendo a [^x](x{n}|x{m})[^x]pero eso sería suponga que siempre hay un carácter detrás y después de usted "x". Como puede ver aquí: https://regex101.com/r/bB2vH2/1

puede cambiarlo a (?:[^x]|^)(x{n}|x{m})(?:[^x]|$), que se traduce en "sin 'x' o siguiente inicio de línea" y "seguido de 'x' o final de línea". Pero aún así, no coincidirá con dos secuencias con un solo carácter entre ellas (porque la primera coincidencia requeriría un carácter después y la segunda un carácter antes) como puede ver aquí: https://regex101.com/r/ oC5oJ4 / 1

Por último, para hacer coincidir la coincidencia distante de un carácter, puede agregar una mirada positiva hacia adelante (? =) En el "no 'x' después" o una mirada positiva detrás (? <=) En el "no 'x' antes", así: https://regex101.com/r/mC4uX3/1

(?<=[^x]|^)(x{n}|x{m})(?:[^x]|$)

De esta manera, coincidirá solo con el número exacto de 'x' que desee.

Endurecido
fuente
1

Echando un vistazo a la respuesta de Enhardened, afirman que su penúltima expresión no coincidirá con secuencias con un solo carácter entre ellas. Hay una manera fácil de solucionar este problema sin usar mirar hacia adelante / mirar atrás, y es reemplazar el carácter de inicio / fin con el carácter de límite. Esto le permite hacer coincidir los límites de las palabras que incluyen inicio / final. Como tal, la expresión apropiada debería ser:

(?:[^x]|\b)(x{n}|x{m})(?:[^x]|\b)

Como puede ver aquí: https://regex101.com/r/oC5oJ4/2 .

rozza2058
fuente
1
Genial, no estaba familiarizado con cómo las expresiones regulares manejaban los límites. El único problema con este método es cuando está utilizando un límite no estándar. Cuente un vistazo: regex101.com/r/j0nkeo/1 y regex101.com/r/4Ix7Dr/1
Enhardened
1
@Enhardened: ese es un buen punto, parece ser un problema con múltiples grupos coincidentes que se superponen. Esa es una situación en la que necesitaría usar mirar hacia atrás.
rozza2058
1

Publicación muy antigua, pero me gustaría contribuir con algo que pueda ser de ayuda. Lo probé exactamente de la manera indicada en la pregunta y funciona, pero hay un problema: el orden de las cantidades importa. Considera esto:

#[a-f0-9]{6}|#[a-f0-9]{3}

Esto encontrará todas las apariciones de códigos de color hexadecimales (tienen 3 o 6 dígitos). Pero cuando le doy la vuelta así

#[a-f0-9]{3}|#[a-f0-9]{6}

solo encontrará los de 3 dígitos o los primeros 3 dígitos de los de 6 dígitos. Esto tiene sentido y un profesional de Regex podría detectarlo de inmediato, pero para muchos esto podría ser un comportamiento peculiar. Hay algunas características avanzadas de Regex que pueden evitar esta trampa independientemente del orden, pero no todos están metidos hasta las rodillas en los patrones de Regex.

DanDan
fuente