<dd id="sga4y"><optgroup id="sga4y"></optgroup></dd>
<nav id="sga4y"><code id="sga4y"></code></nav>
  • <optgroup id="sga4y"></optgroup>

    15.4. 后記

    聰明的讀者在學習前一節時想得會更深入一層。現在寫的這個程序中最令人頭痛的性能負擔是正則表達式,但它是必需的,因為沒有其它方法來識別羅馬數字。但是,它們只有 5000 個,為什么不一次性地構建一個查詢表來讀取?不必用正則表達式凸現了這個主意的好處。你建立了整數到羅馬數字查詢表的時候,羅馬數字到整數的逆向查詢表也構建了。

    更大的好處在于,你已經擁有一整套完全的單元測試。你修改了多半的代碼,但單元測試還是一樣的,因此你可以確定你的新代碼與來的代碼一樣可以正常工作。

    例 15.17. roman9.py

    這個文件可以在例子目錄下的 py/roman/stage9/ 目錄中找到。

    如果您還沒有下載本書附帶的樣例程序, 可以 下載本程序和其他樣例程序

    #Define exceptions
    class RomanError(Exception): pass
    class OutOfRangeError(RomanError): pass
    class NotIntegerError(RomanError): pass
    class InvalidRomanNumeralError(RomanError): pass
    
    #Roman numerals must be less than 5000
    MAX_ROMAN_NUMERAL = 4999
    
    #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))
    
    #Create tables for fast conversion of roman numerals.
    #See fillLookupTables() below.
    toRomanTable = [ None ]  # Skip an index since Roman numerals have no zero
    fromRomanTable = {}
    
    def toRoman(n):
        """convert integer to Roman numeral"""
        if not (0 < n <= MAX_ROMAN_NUMERAL):
            raise OutOfRangeError, "number out of range (must be 1..%s)" % MAX_ROMAN_NUMERAL
        if int(n) <> n:
            raise NotIntegerError, "non-integers can not be converted"
        return toRomanTable[n]
    
    def fromRoman(s):
        """convert Roman numeral to integer"""
        if not s:
            raise InvalidRomanNumeralError, "Input can not be blank"
        if not fromRomanTable.has_key(s):
            raise InvalidRomanNumeralError, "Invalid Roman numeral: %s" % s
        return fromRomanTable[s]
    
    def toRomanDynamic(n):
        """convert integer to Roman numeral using dynamic programming"""
        result = ""
        for numeral, integer in romanNumeralMap:
            if n >= integer:
                result = numeral
                n -= integer
                break
        if n > 0:
            result += toRomanTable[n]
        return result
    
    def fillLookupTables():
        """compute all the possible roman numerals"""
        #Save the values in two global tables to convert to and from integers.
        for integer in range(1, MAX_ROMAN_NUMERAL + 1):
            romanNumber = toRomanDynamic(integer)
            toRomanTable.append(romanNumber)
            fromRomanTable[romanNumber] = integer
    
    fillLookupTables()
    

    這樣有多快呢?

    例 15.18. 用 romantest9.py 測試 roman9.py 的結果

    
    .............
    ----------------------------------------------------------------------
    Ran 13 tests in 0.791s
    
    OK
    
    

    還記得嗎?你原有版本的最快速度是 13 個測試耗時 3.315 秒。當然,這樣的比較不完全公平,因為這個新版本需要更長的時間來導入 (當它填充查詢表時)。但是導入只需一次,在運行過程中可以忽略。

    這個重構的故事的寓意是什么?

    97av