7.4. Uso de la sintaxis {n,m}

En la sección anterior, tratamos con un patrón donde el mismo carácter podía repetirse hasta tres veces. Hay otra manera de expresar esto con expresiones regulares, que algunas personas encuentran más legible. Primero mire el método que hemos usado ya en los ejemplos anteriores.

Ejemplo 7.5. La manera antigua: cada carácter es opcional

>>> import re
>>> pattern = '^M?M?M?$'
>>> re.search(pattern, 'M')    1
<_sre.SRE_Match object at 0x008EE090>
>>> pattern = '^M?M?M?$'
>>> re.search(pattern, 'MM')   2
<_sre.SRE_Match object at 0x008EEB48>
>>> pattern = '^M?M?M?$'
>>> re.search(pattern, 'MMM')  3
<_sre.SRE_Match object at 0x008EE090>
>>> re.search(pattern, 'MMMM') 4
>>> 
1 Esto coincide con el inicio de la cadena, luego la primera M opcional, pero no la segunda y la tercera (pero no pasa nada, porque son opcionales), y luego el fin de la cadena.
2 Esto coincide con el inicio de la cadena, luego la primera y segunda M opcionales, pero no la tercera M (pero no pasa nada, porque es opcional), y luego el fin de la cadena.
3 Esto coincide con el principio de la cadena, y luego con las tres M opcionales, antes del fin de la cadena.
4 Esto coincide con el principio de la cadena, y luego las tres M opcionales, pero no encontramos el fin de la cadena (porque aún hay una M sin emparejar), así que el patrón no coincide y devuelve None.

Ejemplo 7.6. La nueva manera: de n a m

>>> pattern = '^M{0,3}$'       1
>>> re.search(pattern, 'M')    2
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, 'MM')   3
<_sre.SRE_Match object at 0x008EE090>
>>> re.search(pattern, 'MMM')  4
<_sre.SRE_Match object at 0x008EEDA8>
>>> re.search(pattern, 'MMMM') 5
>>> 
1 Este patrón dice: “Coincide con el principio de la cadena, luego cualquier cosa entre cero y tres caracteres M, y luego el final de la cadena”. 0 y 3 podrían ser números cualquiera; si queremos que haya al menos una M pero no más de tres, podríamos escribir M{1,3}.
2 Esto coincide con el principio de la cadena, luego una de tres posibles M, y luego el final de la cadena.
3 Esto coincide con el principio de la cadena, luego dos de tres posibles M, y luego el final de la cadena.
4 Esto coincide con el principio de la cadena, luego tres de tres posibles M, y luego el final de la cadena.
5 Esto coincide con el principio de la cadena, luego tres de tres posibles M, pero entonces no encuentra el final de la cadena. La expresión regular nos permitía sólo hasta tres caracteres M antes de encontrar el fin de la cadena, pero tenemos cuatro, así que el patrón no coincide y se devuelve None.
nota
No hay manera de determinar programáticamente si dos expresiones regulares son equivalentes. Lo mejor que puede hacer es escribir varios casos de prueba para asegurarse de que se comporta correctamente con todos los tipos de entrada relevantes. Hablaremos más adelante en este mismo libro sobre la escritura de casos de prueba.

7.4.1. Comprobación de las decenas y unidades

Ahora expandiremos la expresión regular de números romanos para cubrir las decenas y unidades. Este ejemplo muestra la comprobación de las decenas.

Ejemplo 7.7. Comprobación de las decenas

>>> pattern = '^M?M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)$'
>>> re.search(pattern, 'MCMXL')    1
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, 'MCML')     2
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, 'MCMLX')    3
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, 'MCMLXXX')  4
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, 'MCMLXXXX') 5
>>> 
1 Esto coincide con el principio de la cadena, luego la primera M opcional, después con CM, y XL, para coincidir por último con el fin de la cadena. Recuerde, la sintaxis (A|B|C) significa “coincide exactamente con un patrón de entre A, B o C”. Encontramos XL, así que se ignoran las posibilidades XC y L?X?X?X?, y después pasamos al final de la cadena. MCML es el número romano que representa 1940.
2 Esto coincide con el principio de la cadena, y la primera M opcional, después CM, y L?X?X?X?. De L?X?X?X?, coincide con la L y descarta los tres caracteres opcionales X. Ahora pasamos al final de la cadena. MCML es la representación en romanos de 1950.
3 Esto coincide con el principio de la cadena, luego con la primera M opcional, y con CM, después con la L opcional y con la primera X opcional, ignora las otras dos X opcionales y luego encuentra el final de la cadena. MCMLX representa en números romanos 1960.
4 Esto encuentra el principio de la cadena, luego la primera M opcional, después CM, luego la L opcional y las tres X opcionales, y por último el fin de la cadena. MCMLXXX es el número romano que representa 1980.
5 Esto coincide con el principio de la cadena, y la primera M opcional, luego CM, y después la L y las tres X opcionales, para entonces fallar al intentar coincidir con el fin de la cadena, debido a que aún resta una X sin justificar. Así que el patrón al completo falla, y se devuelve None. MCMLXXXX no es un número romano válido.

La expresión de las unidades sigue el mismo patrón. Le ahorraré los detalles mostrándole el resultado final.

>>> pattern = '^M?M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)(IX|IV|V?I?I?I?)$'

¿Cómo se vería esto usando su sintaxis alternativa {n,m}? El ejemplo nos muestra la nueva sintaxis.

Ejemplo 7.8. Validación de números romanos con {n,m}

>>> pattern = '^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$'
>>> re.search(pattern, 'MDLV')             1
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, 'MMDCLXVI')         2
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, 'MMMMDCCCLXXXVIII') 3
<_sre.SRE_Match object at 0x008EEB48>
>>> re.search(pattern, 'I')                4
<_sre.SRE_Match object at 0x008EEB48>
1 Esto coincide con el principio de la cadena, después con uno de las cuatro M posibles, luego con D?C{0,3}. De estos, coincide con la D opcional y ninguna de las tres posibles C. Seguimos, y coincide con L?X{0,3} con la L opcional y ninguna de las tres posibles X. Entonces coincide con V?I{0,3} al encontrar la V opcional y ninguna de las tres posibles I, y por último el fin de la cadena. MDLV es la representación en romanos de 1555.
2 Esto coincide con el principio de la cadena, luego dos de las cuatro posibles M, entonces con D?C{0,3} por una D y una C de tres posibles; luego con L?X{0,3} por la L y una de tres X posibles; después con V?I{0,3} por una V y una de tres posibles I; y por último, con el fin de la cadena. MMDCLXVI es la representación en números romanos de 2666.
3 Esto coincide con el principio de la cadena, luego con cuatro de cuatro M posibles, después con D?C{0,3} por una D y tres de tres posibles C; entonces con L?X{0,3} por la L y las tres X de tres; luego con V?I{0,3} por la V y tres de tres I; y por último con el final de la cadena. MMMMDCCCLXXXVIII es la representación en números romanos de 3888, y es el número romano más largo que se puede escribir sin usar una sintaxis extendida.
4 Observe atentamente (me siento como un mago. “Observen atentamente, niños, voy a sacar un conejo de mi sombrero.”). Esto coincide con el principio de la cadena, y con ninguna de las cuatro posibles M, luego con D?C{0,3} al ignorar la D y coincidiendo con cero de tres C, después con L?X{0,3} saltándose la L opcional y con cero de tres X, seguidamente con V?I{0,3} ignorando la V opcional y una de tres posibles I. Y por último, el fin de la cadena. ¡Guau!.

Si ha conseguido seguirlo todo y lo ha entendido a la primera, ya lo hizo mejor que yo. Ahora imagínese tratando de entender las expresiones regulares de otra persona, en mitad de una parte crítica de un programa grande. O incluso imagínese encontrando de nuevo sus propias expresiones regulares unos meses más tarde. Me ha sucedido, y no es una visión placentera.

En la siguiente sección exploraremos una sintaxis alternativa que le puede ayudar a hacer mantenibles sus expresiones.