Seguramente ha visto números romanos, incluso si no los conoce.
Puede que los haya visto en copyrights de viejas películas y programas de
televisión (“Copyright MCMXLVI” en lugar
de “Copyright 1946”), o en las placas
conmemorativas en bibliotecas o universidades (“inaugurada en
MDCCCLXXXVIII” en lugar de “inaugurada en
1888”). Puede que incluso los haya visto en
índices o referencias bibliográficas. Es un sistema de representar números
que viene de los tiempos del antiguo imperio romano (de ahí su
nombre).
En los números romanos, existen siete caracteres que representan
números al combinarlos y repetirlos de varias maneras.
Se aplican las siguientes reglas generales al construir números
romanos:
7.3.1. Comprobar los millares
¿Qué se necesitaría para certificar una cadena arbitraria como
número romano válido? Miremos un carácter cada vez. Como los números
romanos se escriben siempre de mayor a menor, empecemos con el mayor: el
lugar de los millares. Para números del 1000 en adelante, los millares se
representan con series de caracteres M
characters.
Ejemplo 7.3. Comprobación de los millares
>>> import re
>>> pattern = '^M?M?M?$'
>>> re.search(pattern, 'M')
<SRE_Match object at 0106FB58>
>>> re.search(pattern, 'MM')
<SRE_Match object at 0106C290>
>>> re.search(pattern, 'MMM')
<SRE_Match object at 0106AA38>
>>> re.search(pattern, 'MMMM')
>>> re.search(pattern, '')
<SRE_Match object at 0106F4A8>
|
Este patrón tiene tres partes:
- ^ para hacer que lo que sigue coincida
sólo con el comienzo de la cadena. Si no se especificase, el patrón podría
coincidir con cualquiera de los caracteres M que
hubiese, que no es lo que deseamos. Quiere asegurarse de que los
caracteres M, si los hay ahí, estén al principio de la
cadena.
- M? para coincidir con un único carácter
M de forma opcional. Como se repite tres veces, estamos
buscando cualquier cosa entre cero y tres caracteres M
seguidos.
- $ para que lo anterior preceda sólo al
fin de la cadena. Cuando se combina con el carácter ^
al principio, significa que el patrón debe coincidir con la cadena al
completo, sin otros caracteres antes o después de las
M.
|
|
La esencia del módulo re es la función search, que toma una
expresión regular (pattern) y una cadena
('M') para comprobar si coincide con la expresión
regular. Si se encuentra una coincidencia, search devuelve un objeto que
tiene varios métodos para describirla; si no hay coincidencia, search
devuelve None, el valor nulo de Python. Todo lo que ha de preocuparnos
por ahora es si el patrón coincide, cosa que podemos saber con sólo mirar
el valor devuelto por search. 'M' coincide con esta
expresión regular, porque se ajusta a la primera M
opcional, mientras que se ignoran la segunda y tercera
M opcionales.
|
|
'MM' también coincide porque se ajusta a los
primeros dos caracteres M opcionales, mientras que se
ignora el tercero.
|
|
'MMM' coincide porque se ajusta a los tres
caracteres M del patrón.
|
|
'MMMM' no coincide. Hay coincidencia con los tres
caracteres M, pero la expresión regular insiste en que
la cadena debe terminar ahí (debido al carácter $),
pero la cadena aún no ha acabado (debido a la cuarta
M). Así que search devuelve None.
|
|
Interesante: una cadena vacía también coincide con esta expresión
regular, ya que todos los caracteres M son
opcionales.
|
7.3.2. Comprobación de centenas
Las centenas son más complejas que los millares, porque hay varias
maneras mutuamente exclusivas de expresarlas, dependiendo de su
valor.
- 100 = C
- 200 = CC
- 300 = CCC
- 400 = CD
- 500 = D
- 600 = DC
- 700 = DCC
- 800 = DCCC
- 900 = CM
Así que hay cuatro patrones posibles:
- CM
- CD
- De cero a tres caracteres C (cero si hay un 0 en el lugar de las centenas)
- D, seguido opcionalmente de hasta tres
caracteres C
Los últimos dos patrones se pueden combinar:
- una D opcional, seguida de hasta tres
caracteres C (opcionales también)
Este ejemplo muestra cómo validar el lugar de las centenas en un
número romano.
Ejemplo 7.4. Comprobación de las centenas
>>> import re
>>> pattern = '^M?M?M?(CM|CD|D?C?C?C?)$'
>>> re.search(pattern, 'MCM')
<SRE_Match object at 01070390>
>>> re.search(pattern, 'MD')
<SRE_Match object at 01073A50>
>>> re.search(pattern, 'MMMCCC')
<SRE_Match object at 010748A8>
>>> re.search(pattern, 'MCMC')
>>> re.search(pattern, '')
<SRE_Match object at 01071D98>
|
Este patrón empieza igual que el anterior, comprobando el principio
de la cadena, (^), luego el lugar de los millares
(M?M?M?). Aquí viene la parte nueva, entre paréntesis,
que define un conjunto de tres patrones mutuamente exclusivos, separados
por barras verticales: CM, CD, y
D?C?C?C? (que es una D opcional
seguida de cero a tres caracteres C opcionales). El
analizador de expresiones regulares comprueba cada uno de estos patrones
en orden (de izquierda a derecha), toma el primero que coincida, y
descarta el resto.
|
|
'MCM' coincide porque la primera
M lo hace, ignorando los dos siguientes caracteres
M, y CM coincide (así que no se
llegan a considerar los patrones CD ni
D?C?C?C?). MCM es la representación
en números romanos de 1900.
|
|
'MD' coincide con la primera
M, ignorando la segunda y tercera M,
y el patrón D?C?C?C? coincide con la
D (cada uno de los tres caracteres C
son opcionales así que se ignoran). MD es la
representación en romanos de 1500.
|
|
'MMMCCC' coincide con los tres primeros
caracteres M, y con el patrón
D?C?C?C? coinciden CCC (la
D es opcional y se ignora). MMMCCC
es la representación en números romanos de 3300.
|
|
'MCMC' no coincide. La primera
M lo hace, las dos siguientes se ignoran, y también
coincide CM, pero $ no lo hace, ya
que aún no hemos llegado al final de la cadena de caracteres (todavía nos
queda un carácter C sin pareja). La
C no coincide como parte del
patrón D?C?C?C?, ya que se encontró antes el patrón
CM, mutuamente exclusivo.
|
|
Es interesante ver que la cadena vacía sigue coincidiendo con este
patrón, porque todas las M son opcionales y se ignoran,
y porque la cadena vacía coincide con el patrón
D?C?C?C?, en el que todos los caracteres son
opcionales, y por tanto también se ignoran.
|
¡Vaya! ¿Ve lo rápido que las expresiones regulares se vuelven feas?
Y sólo hemos cubierto los millares y las centenas de los números romanos.
Pero si ha seguido el razonamiento, las decenas y las unidades son
sencillas, porque siguen exactamente el mismo patrón de las centenas. Pero
veamos otra manera de expresarlo.