7.9. roman.py, fase 4

Ora che la funzione toRoman è completa, è tempo di cominciare a scrivere il codice di fromRoman. Grazie alla nostra struttura dati dettagliata che mappa i singoli numeri romani elementari negli interi corrispondenti, la cosa non è più difficile dell'aver scritto la funzione toRoman.

Esempio 7.15. roman4.py

Se non lo avete ancora fatto, potete scaricare questo ed altri esempi usati in questo libro.

"""Convert to and from Roman numerals"""

#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))

# toRoman function omitted for clarity (it hasn’t changed)

def fromRoman(s):
    """convert Roman numeral to integer"""
    result = 0
    index = 0
    for numeral, integer in romanNumeralMap:
        while s[index:index+len(numeral)] == numeral: 1
            result += integer
            index += len(numeral)
    return result
1 Lo schema qui è lo stesso che in toRoman. Si itera sulla struttura dati che definisce i numeri romani (una tupla di tuple) ed invece che cercare il valore intero più alto, fino a che ciò risulta possibile, cerchiamo il numero romano “più alto”, sempre fino a che ciò risulta possibile.

Esempio 7.16. Come funziona fromRoman

Se non è chiaro come funziona fromRoman, potete aggiungere un'istruzione di stampa alla fine del ciclo while:

        while s[index:index+len(numeral)] == numeral:
            result += integer
            index += len(numeral)
            print 'found', numeral, ', adding', integer
>>> import roman4
>>> roman4.fromRoman('MCMLXXII')
found M , adding 1000
found CM , adding 900
found L , adding 50
found X , adding 10
found X , adding 10
found I , adding 1
found I , adding 1
1972

Esempio 7.17. Output di romantest4.py a fronte di roman4.py

fromRoman should only accept uppercase input ... FAIL
toRoman should always return uppercase ... ok
fromRoman should fail with malformed antecedents ... FAIL
fromRoman should fail with repeated pairs of numerals ... FAIL
fromRoman should fail with too many repeated numerals ... FAIL
fromRoman should give known result with known input ... ok 1
toRoman should give known result with known input ... ok
fromRoman(toRoman(n))==n for all n ... ok                  2
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
1 Due notizie importanti qui. La prima è che fromRoman funziona per input validi, almeno per i valori noti che usiamo per il test.
2 La seconda è che il nostro test di consistenza è anch'esso superato con successo. Combinato con il test sui valori noti, possiamo essere ragionevolmente sicuri che sia toRoman che fromRoman funzionino in modo corretto con tutti i valori noti. (La cosa non è garantita: è teoricamente possibile che toRoman abbia un baco che produca il numero romano sbagliato per qualche particolare insieme di input e che fromRoman abbia un baco reciproco rispetto al primo che produca lo stesso insieme di valori interi per i quali toRoman produce i numeri romani sbagliati. A seconda dell'uso previsto delle funzioni e dei loro requisiti, questo può essere o meno una preoccupazione reale; se lo è, potete scrivete un test sufficientemente comprensivo da escludere tale preoccupazione.)

======================================================================
FAIL: fromRoman should only accept uppercase input
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\docbook\dip\py\roman\stage4\romantest4.py", line 156, in testFromRomanCase
    roman4.fromRoman, numeral.lower())
  File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises
    raise self.failureException, excName
AssertionError: InvalidRomanNumeralError
======================================================================
FAIL: fromRoman should fail with malformed antecedents
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\docbook\dip\py\roman\stage4\romantest4.py", line 133, in testMalformedAntecedent
    self.assertRaises(roman4.InvalidRomanNumeralError, roman4.fromRoman, s)
  File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises
    raise self.failureException, excName
AssertionError: InvalidRomanNumeralError
======================================================================
FAIL: fromRoman should fail with repeated pairs of numerals
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\docbook\dip\py\roman\stage4\romantest4.py", line 127, in testRepeatedPairs
    self.assertRaises(roman4.InvalidRomanNumeralError, roman4.fromRoman, s)
  File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises
    raise self.failureException, excName
AssertionError: InvalidRomanNumeralError
======================================================================
FAIL: fromRoman should fail with too many repeated numerals
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\docbook\dip\py\roman\stage4\romantest4.py", line 122, in testTooManyRepeatedNumerals
    self.assertRaises(roman4.InvalidRomanNumeralError, roman4.fromRoman, s)
  File "c:\python21\lib\unittest.py", line 266, in failUnlessRaises
    raise self.failureException, excName
AssertionError: InvalidRomanNumeralError
----------------------------------------------------------------------
Ran 12 tests in 1.222s

FAILED (failures=4)