Zanurkuj w Pythonie/roman.py, etap 4

Implementacja funkcji toRoman została zakończona, czas zająć się funkcją fromRoman. Dzięki bogatej strukturze danych przechowującej pewne wartości w reprezentacji rzymskiej wraz z ich wartościami liczbowymi, zadanie to nie będzie wcale trudniejsze, niż napisanie funkcji toRoman.

Przykład 14.9. roman4.py

Plik jest dostępny w katalogu in py/roman/stage4/ wewnątrz katalogu examples.

Jeśli jeszcze tego nie zrobiliście, możecie pobrać ten oraz inne przykłady używane w tej książce stąd.

"""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. Sposób działania jest taki sam jak w toRoman. Iterujemy po reprezentacjach rzymskich w strukturze danych (będącej krotką krotek), jednak zamiast dopasowywania największej wartości całkowitej tak często, jak to możliwe, dopasowujemy „najwyższą” reprezentację rzymską tak często, jak to możliwe.

Przykład 14.10. Jak działa fromRoman

Jeśli wciąż nie jesteście pewni, jak działa fromRoman, na końcu pętli while dodajcie instrukcję print:

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

Przykład 14.11. Wyjście programu romantest4.py testującego 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. Mamy tu dwie interesujące wiadomości: po pierwsze, fromRoman działa dla poprawnych danych wejściowych, przynajmniej dla tych, które są zdefiniowane w teście poprawnych wartości.
  2. Po drugie, test zdroworozsądkowy również przeszedł. Wiedząc o tym, że przeszedł również test znanych wartości, możemy być raczej pewni, że zarówno fromRoman jak i toRoman działają poprawnie dla poprawnych danych wejściowych. (Nic nam tego jednak nie gwarantuje; teoretycznie jest możliwe, że w funkcji toRoman ukryty jest jakiś błąd, przez który dla pewnego zestawu danych wejściowych generowane są niepoprawne reprezentacje rzymskie, natomiast fromRoman zawierać może symetryczny błąd, który z kolei powoduje, że dla tych właśnie rzymskich reprezentacji generowane są niepoprawne wartości liczbowe. W zależności od zastosowań waszego kodu, a także wymagań, jakim ten kod podlega, może to stanowić dla was pewien problem; jeśli tak jest, dopiszcie więcej bardziej wszechstronnych testów tak, aby zmniejszyła się wasza niepewność.)
======================================================================
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)