| You are here: Inicio > Inmersión en Python > Programación Test-First > roman.py, fase 5 | << >> | ||||
Inmersión en PythonPython de novato a experto |
|||||
Ahora que fromRoman funciona adecuadamente con entradas correctas es el momento de encajar la última pieza del puzzle: hacer que funcione adecuadamente con entradas incorrectas. Esto implica buscar una manera de mirar una cadena y determinar si es un número romano válido. Esto es inherentemente más difícil que validar entrada numérica en toRoman, pero tenemos una herramienta potente a nuestra disposición: expresiones regulares.
Si no le son familiares las expresiones regulares y no ha leído el capítulo Capítulo 7, Expresiones regulares, ahora puede ser un buen momento.
Como vio en Sección 7.3, “Caso de estudio: números romanos”, hay varias reglas simples para construir un número romano usando las letras M, D, C, L, X, V, e I. Revisemos las reglas:
Este fichero está disponible en py/roman/stage5/ dentro del directorio de ejemplos.
Si aún no lo ha hecho, puede descargar éste ejemplo y otros usados en este libro.
"""Convert to and from Roman numerals""" import re #Define exceptions class RomanError(Exception): pass class OutOfRangeError(RomanError): pass class NotIntegerError(RomanError): pass class InvalidRomanNumeralError(RomanError): pass #Define digit mapping romanNumeralMap = (('M', 1000), ('CM', 900), ('D', 500), ('CD', 400), ('C', 100), ('XC', 90), ('L', 50), ('XL', 40), ('X', 10), ('IX', 9), ('V', 5), ('IV', 4), ('I', 1)) def toRoman(n): """convert integer to Roman numeral""" if not (0 < n < 4000): raise OutOfRangeError, "number out of range (must be 1..3999)" if int(n) <> n: raise NotIntegerError, "non-integers can not be converted" result = "" for numeral, integer in romanNumeralMap: while n >= integer: result += numeral n -= integer return result #Define pattern to detect valid Roman numerals romanNumeralPattern = '^M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)(IX|IV|V?I?I?I?)$'def fromRoman(s): """convert Roman numeral to integer""" if not re.search(romanNumeralPattern, s):
raise InvalidRomanNumeralError, 'Invalid Roman numeral: %s' % s result = 0 index = 0 for numeral, integer in romanNumeralMap: while s[index:index+len(numeral)] == numeral: result += integer index += len(numeral) return result
| Esto es sólo una continuación del patrón que comentamos en Sección 7.3, “Caso de estudio: números romanos”. Los lugares de las decenas son XC (90), XL (40), o una L opcional seguida de 0 a 3 caracteres X opcionales. El lugar de las unidades es IX (9), IV (4), o una V opcional seguida de 0 a 3 caracteres I opcionales. | |
| Habiendo codificado todas esta lógica en una expresión regular, el código para comprobar un número romano inválido se vuelve trivial. Si re.search devuelve un objeto entonces la expresión regular ha coincidido y la entrada es válida; si no, la entrada es inválida. |
Llegados aquí, se le permite ser escéptico al pensar que esa expresión regular grande y fea pueda capturar posiblemente todos los tipos de números romanos no válidos. Pero no se limite a aceptar mi palabra, mire los resultados:
fromRoman should only accept uppercase input ... oktoRoman should always return uppercase ... ok fromRoman should fail with malformed antecedents ... ok
fromRoman should fail with repeated pairs of numerals ... ok
fromRoman should fail with too many repeated numerals ... ok fromRoman should give known result with known input ... ok toRoman should give known result with known input ... ok fromRoman(toRoman(n))==n for all n ... ok toRoman should fail with non-integer input ... ok toRoman should fail with negative input ... ok toRoman should fail with large input ... ok toRoman should fail with 0 input ... ok ---------------------------------------------------------------------- Ran 12 tests in 2.864s OK
| Cuando hayan pasado todas sus pruebas, deje de programar. | |
<< roman.py, fase 4 |
| 1 | 2 | 3 | 4 | 5 | |
Refactorización >> |