diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..9e32b48 --- /dev/null +++ b/.hgignore @@ -0,0 +1,2 @@ +syntax: glob +*.pyc diff --git a/regular-expressions.html b/regular-expressions.html index b720678..23aaa1d 100644 --- a/regular-expressions.html +++ b/regular-expressions.html @@ -209,7 +209,7 @@ characters. If you've used regular expressions in other languages (like Perl), t
Now let's expand the Roman numeral regular expression to cover the tens and ones place. This example shows the check for tens.
->>> pattern = '^M?M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)$' +>>> pattern = '^M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)$' >>> re.search(pattern, 'MCMXL') ① <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, 'MCML') ② @@ -229,10 +229,10 @@ characters. If you've used regular expressions in other languages (like Perl), tThe expression for the ones place follows the same pattern. I'll spare you the details and show you the end result.
->>> pattern = '^M?M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)(IX|IV|V?I?I?I?)$' +>>> pattern = '^M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)(IX|IV|V?I?I?I?)$'So what does that look like using this alternate
{n,m}syntax? This example shows the new syntax.->>> pattern = '^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$' +>>> pattern = '^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$' >>> re.search(pattern, 'MDLV') ① <_sre.SRE_Match object at 0x008EEB48> >>> re.search(pattern, 'MMDCLXVI') ② @@ -259,15 +259,15 @@ characters. If you've used regular expressions in other languages (like Perl), tThis will be more clear with an example. Let's revisit the compact regular expression you've been working with, and make it a verbose regular expression. This example shows how.
>>> pattern = """ - ^ # beginning of string - M{0,4} # thousands - 0 to 4 M's + ^ # beginning of string + M{0,3} # thousands - 0 to 3 M's (CM|CD|D?C{0,3}) # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's), - # or 500-800 (D, followed by 0 to 3 C's) + # or 500-800 (D, followed by 0 to 3 C's) (XC|XL|L?X{0,3}) # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's), - # or 50-80 (L, followed by 0 to 3 X's) + # or 50-80 (L, followed by 0 to 3 X's) (IX|IV|V?I{0,3}) # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's), - # or 5-8 (V, followed by 0 to 3 I's) - $ # end of string + # or 5-8 (V, followed by 0 to 3 I's) + $ # end of string """ >>> re.search(pattern, 'M', re.VERBOSE) ① <_sre.SRE_Match object at 0x008EEB48> diff --git a/roman1.py b/roman1.py new file mode 100644 index 0000000..e051037 --- /dev/null +++ b/roman1.py @@ -0,0 +1,33 @@ +"""Convert to and from Roman numerals + +This program is part of "Dive Into Python 3", a free Python book for +experienced programmers. Visit http://diveintopython3.org/ for the +latest version. +""" + +roman_numeral_map = (('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 to_roman(n): + """convert integer to Roman numeral""" + result = "" + for numeral, integer in roman_numeral_map: + while n >= integer: + result += numeral + n -= integer + return result + +def from_roman(s): + """convert Roman numeral to integer""" + pass diff --git a/roman2.py b/roman2.py new file mode 100644 index 0000000..285abce --- /dev/null +++ b/roman2.py @@ -0,0 +1,37 @@ +"""Convert to and from Roman numerals + +This program is part of "Dive Into Python 3", a free Python book for +experienced programmers. Visit http://diveintopython3.org/ for the +latest version. +""" +class OutOfRangeError(ValueError): pass + +roman_numeral_map = (('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 to_roman(n): + """convert integer to Roman numeral""" + if n > 3999: + raise OutOfRangeError("number out of range (must be less than 3999)") + + result = "" + for numeral, integer in roman_numeral_map: + while n >= integer: + result += numeral + n -= integer + return result + +def from_roman(s): + """convert Roman numeral to integer""" + pass diff --git a/roman3.py b/roman3.py new file mode 100644 index 0000000..832dfcf --- /dev/null +++ b/roman3.py @@ -0,0 +1,37 @@ +"""Convert to and from Roman numerals + +This program is part of "Dive Into Python 3", a free Python book for +experienced programmers. Visit http://diveintopython3.org/ for the +latest version. +""" +class OutOfRangeError(ValueError): pass + +roman_numeral_map = (('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 to_roman(n): + """convert integer to Roman numeral""" + if not (0 < n < 4000): + raise OutOfRangeError("number out of range (must be 0..3999)") + + result = "" + for numeral, integer in roman_numeral_map: + while n >= integer: + result += numeral + n -= integer + return result + +def from_roman(s): + """convert Roman numeral to integer""" + pass diff --git a/roman4.py b/roman4.py new file mode 100644 index 0000000..e0ddc8b --- /dev/null +++ b/roman4.py @@ -0,0 +1,40 @@ +"""Convert to and from Roman numerals + +This program is part of "Dive Into Python 3", a free Python book for +experienced programmers. Visit http://diveintopython3.org/ for the +latest version. +""" +class OutOfRangeError(ValueError): pass +class NotIntegerError(ValueError): pass + +roman_numeral_map = (('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 to_roman(n): + """convert integer to Roman numeral""" + if not (0 < n < 4000): + raise OutOfRangeError("number out of range (must be 0..3999)") + if not isinstance(n, int): + raise NotIntegerError("non-integers can not be converted") + + result = "" + for numeral, integer in roman_numeral_map: + while n >= integer: + result += numeral + n -= integer + return result + +def from_roman(s): + """convert Roman numeral to integer""" + pass diff --git a/roman5.py b/roman5.py new file mode 100644 index 0000000..04e3046 --- /dev/null +++ b/roman5.py @@ -0,0 +1,46 @@ +"""Convert to and from Roman numerals + +This program is part of "Dive Into Python 3", a free Python book for +experienced programmers. Visit http://diveintopython3.org/ for the +latest version. +""" +class OutOfRangeError(ValueError): pass +class NotIntegerError(ValueError): pass + +roman_numeral_map = (('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 to_roman(n): + """convert integer to Roman numeral""" + if not (0 < n < 4000): + raise OutOfRangeError("number out of range (must be 0..3999)") + if not isinstance(n, int): + raise NotIntegerError("non-integers can not be converted") + + result = "" + for numeral, integer in roman_numeral_map: + while n >= integer: + result += numeral + n -= integer + return result + +def from_roman(s): + """convert Roman numeral to integer""" + result = 0 + index = 0 + for numeral, integer in roman_numeral_map: + while s[index : index + len(numeral)] == numeral: + result += integer + index += len(numeral) + return result diff --git a/roman6.py b/roman6.py new file mode 100644 index 0000000..47f450c --- /dev/null +++ b/roman6.py @@ -0,0 +1,64 @@ +"""Convert to and from Roman numerals + +This program is part of "Dive Into Python 3", a free Python book for +experienced programmers. Visit http://diveintopython3.org/ for the +latest version. +""" +import re + +class OutOfRangeError(ValueError): pass +class NotIntegerError(ValueError): pass +class InvalidRomanNumeralError(ValueError): pass + +roman_numeral_map = (('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)) + +roman_numeral_pattern = re.compile(""" + ^ # beginning of string + M{0,3} # thousands - 0 to 3 M's + (CM|CD|D?C{0,3}) # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's), + # or 500-800 (D, followed by 0 to 3 C's) + (XC|XL|L?X{0,3}) # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's), + # or 50-80 (L, followed by 0 to 3 X's) + (IX|IV|V?I{0,3}) # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's), + # or 5-8 (V, followed by 0 to 3 I's) + $ # end of string + """, re.VERBOSE) + +def to_roman(n): + """convert integer to Roman numeral""" + if not (0 < n < 4000): + raise OutOfRangeError("number out of range (must be 0..3999)") + if not isinstance(n, int): + raise NotIntegerError("non-integers can not be converted") + + result = "" + for numeral, integer in roman_numeral_map: + while n >= integer: + result += numeral + n -= integer + return result + +def from_roman(s): + """convert Roman numeral to integer""" + if not roman_numeral_pattern.search(s): + raise InvalidRomanNumeralError("Invalid Roman numeral: {0}".format(s)) + + result = 0 + index = 0 + for numeral, integer in roman_numeral_map: + while s[index : index + len(numeral)] == numeral: + result += integer + index += len(numeral) + return result diff --git a/roman7.py b/roman7.py new file mode 100644 index 0000000..e848e59 --- /dev/null +++ b/roman7.py @@ -0,0 +1,66 @@ +"""Convert to and from Roman numerals + +This program is part of "Dive Into Python 3", a free Python book for +experienced programmers. Visit http://diveintopython3.org/ for the +latest version. +""" +import re + +class OutOfRangeError(ValueError): pass +class NotIntegerError(ValueError): pass +class InvalidRomanNumeralError(ValueError): pass + +roman_numeral_map = (('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)) + +roman_numeral_pattern = re.compile(""" + ^ # beginning of string + M{0,3} # thousands - 0 to 3 M's + (CM|CD|D?C{0,3}) # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's), + # or 500-800 (D, followed by 0 to 3 C's) + (XC|XL|L?X{0,3}) # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's), + # or 50-80 (L, followed by 0 to 3 X's) + (IX|IV|V?I{0,3}) # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's), + # or 5-8 (V, followed by 0 to 3 I's) + $ # end of string + """, re.VERBOSE) + +def to_roman(n): + """convert integer to Roman numeral""" + if not (0 < n < 4000): + raise OutOfRangeError("number out of range (must be 0..3999)") + if not isinstance(n, int): + raise NotIntegerError("non-integers can not be converted") + + result = "" + for numeral, integer in roman_numeral_map: + while n >= integer: + result += numeral + n -= integer + return result + +def from_roman(s): + """convert Roman numeral to integer""" + if not s: + raise InvalidRomanNumeralError("Input can not be blank") + if not roman_numeral_pattern.search(s): + raise InvalidRomanNumeralError("Invalid Roman numeral: {0}".format(s)) + + result = 0 + index = 0 + for numeral, integer in roman_numeral_map: + while s[index : index + len(numeral)] == numeral: + result += integer + index += len(numeral) + return result diff --git a/roman8.py b/roman8.py new file mode 100644 index 0000000..124bbbc --- /dev/null +++ b/roman8.py @@ -0,0 +1,68 @@ +"""Convert to and from Roman numerals + +This program is part of "Dive Into Python 3", a free Python book for +experienced programmers. Visit http://diveintopython3.org/ for the +latest version. +""" +import re + +class OutOfRangeError(ValueError): pass +class NotIntegerError(ValueError): pass +class InvalidRomanNumeralError(ValueError): pass + +roman_numeral_map = (('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)) + +roman_numeral_pattern = re.compile(""" + ^ # beginning of string + M{0,3} # thousands - 0 to 3 M's + (CM|CD|D?C{0,3}) # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's), + # or 500-800 (D, followed by 0 to 3 C's) + (XC|XL|L?X{0,3}) # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's), + # or 50-80 (L, followed by 0 to 3 X's) + (IX|IV|V?I{0,3}) # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's), + # or 5-8 (V, followed by 0 to 3 I's) + $ # end of string + """, re.VERBOSE) + +def to_roman(n): + """convert integer to Roman numeral""" + if not (0 < n < 4000): + raise OutOfRangeError("number out of range (must be 0..3999)") + if not isinstance(n, int): + raise NotIntegerError("non-integers can not be converted") + + result = "" + for numeral, integer in roman_numeral_map: + while n >= integer: + result += numeral + n -= integer + return result + +def from_roman(s): + """convert Roman numeral to integer""" + if not isinstance(s, str): + raise InvalidRomanNumeralError("Input must be a string") + if not s: + raise InvalidRomanNumeralError("Input can not be blank") + if not roman_numeral_pattern.search(s): + raise InvalidRomanNumeralError("Invalid Roman numeral: {0}".format(s)) + + result = 0 + index = 0 + for numeral, integer in roman_numeral_map: + while s[index : index + len(numeral)] == numeral: + result += integer + index += len(numeral) + return result diff --git a/romantest1.py b/romantest1.py new file mode 100644 index 0000000..bc75857 --- /dev/null +++ b/romantest1.py @@ -0,0 +1,76 @@ +"""Unit test for roman1.py + +This program is part of "Dive Into Python 3", a free Python book for +experienced programmers. Visit http://diveintopython3.org/ for the +latest version. +""" + +import roman1 +import unittest + +class KnownValues(unittest.TestCase): + known_values = ( (1, 'I'), + (2, 'II'), + (3, 'III'), + (4, 'IV'), + (5, 'V'), + (6, 'VI'), + (7, 'VII'), + (8, 'VIII'), + (9, 'IX'), + (10, 'X'), + (50, 'L'), + (100, 'C'), + (500, 'D'), + (1000, 'M'), + (31, 'XXXI'), + (148, 'CXLVIII'), + (294, 'CCXCIV'), + (312, 'CCCXII'), + (421, 'CDXXI'), + (528, 'DXXVIII'), + (621, 'DCXXI'), + (782, 'DCCLXXXII'), + (870, 'DCCCLXX'), + (941, 'CMXLI'), + (1043, 'MXLIII'), + (1110, 'MCX'), + (1226, 'MCCXXVI'), + (1301, 'MCCCI'), + (1485, 'MCDLXXXV'), + (1509, 'MDIX'), + (1607, 'MDCVII'), + (1754, 'MDCCLIV'), + (1832, 'MDCCCXXXII'), + (1993, 'MCMXCIII'), + (2074, 'MMLXXIV'), + (2152, 'MMCLII'), + (2212, 'MMCCXII'), + (2343, 'MMCCCXLIII'), + (2499, 'MMCDXCIX'), + (2574, 'MMDLXXIV'), + (2646, 'MMDCXLVI'), + (2723, 'MMDCCXXIII'), + (2892, 'MMDCCCXCII'), + (2975, 'MMCMLXXV'), + (3051, 'MMMLI'), + (3185, 'MMMCLXXXV'), + (3250, 'MMMCCL'), + (3313, 'MMMCCCXIII'), + (3408, 'MMMCDVIII'), + (3501, 'MMMDI'), + (3610, 'MMMDCX'), + (3743, 'MMMDCCXLIII'), + (3844, 'MMMDCCCXLIV'), + (3888, 'MMMDCCCLXXXVIII'), + (3940, 'MMMCMXL'), + (3999, 'MMMCMXCIX')) + + def test_to_roman_known_values(self): + """to_roman should give known result with known input""" + for integer, numeral in self.known_values: + result = roman1.to_roman(integer) + self.assertEqual(numeral, result) + +if __name__ == "__main__": + unittest.main() diff --git a/romantest2.py b/romantest2.py new file mode 100644 index 0000000..f111f1b --- /dev/null +++ b/romantest2.py @@ -0,0 +1,81 @@ +"""Unit test for roman1.py + +This program is part of "Dive Into Python 3", a free Python book for +experienced programmers. Visit http://diveintopython3.org/ for the +latest version. +""" + +import roman2 +import unittest + +class KnownValues(unittest.TestCase): + known_values = ( (1, 'I'), + (2, 'II'), + (3, 'III'), + (4, 'IV'), + (5, 'V'), + (6, 'VI'), + (7, 'VII'), + (8, 'VIII'), + (9, 'IX'), + (10, 'X'), + (50, 'L'), + (100, 'C'), + (500, 'D'), + (1000, 'M'), + (31, 'XXXI'), + (148, 'CXLVIII'), + (294, 'CCXCIV'), + (312, 'CCCXII'), + (421, 'CDXXI'), + (528, 'DXXVIII'), + (621, 'DCXXI'), + (782, 'DCCLXXXII'), + (870, 'DCCCLXX'), + (941, 'CMXLI'), + (1043, 'MXLIII'), + (1110, 'MCX'), + (1226, 'MCCXXVI'), + (1301, 'MCCCI'), + (1485, 'MCDLXXXV'), + (1509, 'MDIX'), + (1607, 'MDCVII'), + (1754, 'MDCCLIV'), + (1832, 'MDCCCXXXII'), + (1993, 'MCMXCIII'), + (2074, 'MMLXXIV'), + (2152, 'MMCLII'), + (2212, 'MMCCXII'), + (2343, 'MMCCCXLIII'), + (2499, 'MMCDXCIX'), + (2574, 'MMDLXXIV'), + (2646, 'MMDCXLVI'), + (2723, 'MMDCCXXIII'), + (2892, 'MMDCCCXCII'), + (2975, 'MMCMLXXV'), + (3051, 'MMMLI'), + (3185, 'MMMCLXXXV'), + (3250, 'MMMCCL'), + (3313, 'MMMCCCXIII'), + (3408, 'MMMCDVIII'), + (3501, 'MMMDI'), + (3610, 'MMMDCX'), + (3743, 'MMMDCCXLIII'), + (3844, 'MMMDCCCXLIV'), + (3888, 'MMMDCCCLXXXVIII'), + (3940, 'MMMCMXL'), + (3999, 'MMMCMXCIX')) + + def test_to_roman_known_values(self): + """to_roman should give known result with known input""" + for integer, numeral in self.known_values: + result = roman2.to_roman(integer) + self.assertEqual(numeral, result) + +class ToRomanBadInput(unittest.TestCase): + def test_too_large(self): + """to_roman should fail with large input""" + self.assertRaises(roman2.OutOfRangeError, roman2.to_roman, 4000) + +if __name__ == "__main__": + unittest.main() diff --git a/romantest3.py b/romantest3.py new file mode 100644 index 0000000..b759806 --- /dev/null +++ b/romantest3.py @@ -0,0 +1,89 @@ +"""Unit test for roman1.py + +This program is part of "Dive Into Python 3", a free Python book for +experienced programmers. Visit http://diveintopython3.org/ for the +latest version. +""" + +import roman3 +import unittest + +class KnownValues(unittest.TestCase): + known_values = ( (1, 'I'), + (2, 'II'), + (3, 'III'), + (4, 'IV'), + (5, 'V'), + (6, 'VI'), + (7, 'VII'), + (8, 'VIII'), + (9, 'IX'), + (10, 'X'), + (50, 'L'), + (100, 'C'), + (500, 'D'), + (1000, 'M'), + (31, 'XXXI'), + (148, 'CXLVIII'), + (294, 'CCXCIV'), + (312, 'CCCXII'), + (421, 'CDXXI'), + (528, 'DXXVIII'), + (621, 'DCXXI'), + (782, 'DCCLXXXII'), + (870, 'DCCCLXX'), + (941, 'CMXLI'), + (1043, 'MXLIII'), + (1110, 'MCX'), + (1226, 'MCCXXVI'), + (1301, 'MCCCI'), + (1485, 'MCDLXXXV'), + (1509, 'MDIX'), + (1607, 'MDCVII'), + (1754, 'MDCCLIV'), + (1832, 'MDCCCXXXII'), + (1993, 'MCMXCIII'), + (2074, 'MMLXXIV'), + (2152, 'MMCLII'), + (2212, 'MMCCXII'), + (2343, 'MMCCCXLIII'), + (2499, 'MMCDXCIX'), + (2574, 'MMDLXXIV'), + (2646, 'MMDCXLVI'), + (2723, 'MMDCCXXIII'), + (2892, 'MMDCCCXCII'), + (2975, 'MMCMLXXV'), + (3051, 'MMMLI'), + (3185, 'MMMCLXXXV'), + (3250, 'MMMCCL'), + (3313, 'MMMCCCXIII'), + (3408, 'MMMCDVIII'), + (3501, 'MMMDI'), + (3610, 'MMMDCX'), + (3743, 'MMMDCCXLIII'), + (3844, 'MMMDCCCXLIV'), + (3888, 'MMMDCCCLXXXVIII'), + (3940, 'MMMCMXL'), + (3999, 'MMMCMXCIX')) + + def test_to_roman_known_values(self): + """to_roman should give known result with known input""" + for integer, numeral in self.known_values: + result = roman3.to_roman(integer) + self.assertEqual(numeral, result) + +class ToRomanBadInput(unittest.TestCase): + def test_too_large(self): + """to_roman should fail with large input""" + self.assertRaises(roman3.OutOfRangeError, roman3.to_roman, 4000) + + def test_zero(self): + """to_roman should fail with 0 input""" + self.assertRaises(roman3.OutOfRangeError, roman3.to_roman, 0) + + def test_negative(self): + """to_roman should fail with negative input""" + self.assertRaises(roman3.OutOfRangeError, roman3.to_roman, -1) + +if __name__ == "__main__": + unittest.main() diff --git a/romantest4.py b/romantest4.py new file mode 100644 index 0000000..8a9f37a --- /dev/null +++ b/romantest4.py @@ -0,0 +1,93 @@ +"""Unit test for roman1.py + +This program is part of "Dive Into Python 3", a free Python book for +experienced programmers. Visit http://diveintopython3.org/ for the +latest version. +""" + +import roman4 +import unittest + +class KnownValues(unittest.TestCase): + known_values = ( (1, 'I'), + (2, 'II'), + (3, 'III'), + (4, 'IV'), + (5, 'V'), + (6, 'VI'), + (7, 'VII'), + (8, 'VIII'), + (9, 'IX'), + (10, 'X'), + (50, 'L'), + (100, 'C'), + (500, 'D'), + (1000, 'M'), + (31, 'XXXI'), + (148, 'CXLVIII'), + (294, 'CCXCIV'), + (312, 'CCCXII'), + (421, 'CDXXI'), + (528, 'DXXVIII'), + (621, 'DCXXI'), + (782, 'DCCLXXXII'), + (870, 'DCCCLXX'), + (941, 'CMXLI'), + (1043, 'MXLIII'), + (1110, 'MCX'), + (1226, 'MCCXXVI'), + (1301, 'MCCCI'), + (1485, 'MCDLXXXV'), + (1509, 'MDIX'), + (1607, 'MDCVII'), + (1754, 'MDCCLIV'), + (1832, 'MDCCCXXXII'), + (1993, 'MCMXCIII'), + (2074, 'MMLXXIV'), + (2152, 'MMCLII'), + (2212, 'MMCCXII'), + (2343, 'MMCCCXLIII'), + (2499, 'MMCDXCIX'), + (2574, 'MMDLXXIV'), + (2646, 'MMDCXLVI'), + (2723, 'MMDCCXXIII'), + (2892, 'MMDCCCXCII'), + (2975, 'MMCMLXXV'), + (3051, 'MMMLI'), + (3185, 'MMMCLXXXV'), + (3250, 'MMMCCL'), + (3313, 'MMMCCCXIII'), + (3408, 'MMMCDVIII'), + (3501, 'MMMDI'), + (3610, 'MMMDCX'), + (3743, 'MMMDCCXLIII'), + (3844, 'MMMDCCCXLIV'), + (3888, 'MMMDCCCLXXXVIII'), + (3940, 'MMMCMXL'), + (3999, 'MMMCMXCIX')) + + def test_to_roman_known_values(self): + """to_roman should give known result with known input""" + for integer, numeral in self.known_values: + result = roman4.to_roman(integer) + self.assertEqual(numeral, result) + +class ToRomanBadInput(unittest.TestCase): + def test_too_large(self): + """to_roman should fail with large input""" + self.assertRaises(roman4.OutOfRangeError, roman4.to_roman, 4000) + + def test_zero(self): + """to_roman should fail with 0 input""" + self.assertRaises(roman4.OutOfRangeError, roman4.to_roman, 0) + + def test_negative(self): + """to_roman should fail with negative input""" + self.assertRaises(roman4.OutOfRangeError, roman4.to_roman, -1) + + def test_non_integer(self): + """to_roman should fail with non-integer input""" + self.assertRaises(roman4.NotIntegerError, roman4.to_roman, 0.5) + +if __name__ == "__main__": + unittest.main() diff --git a/romantest5.py b/romantest5.py new file mode 100644 index 0000000..d3f9d16 --- /dev/null +++ b/romantest5.py @@ -0,0 +1,107 @@ +"""Unit test for roman1.py + +This program is part of "Dive Into Python 3", a free Python book for +experienced programmers. Visit http://diveintopython3.org/ for the +latest version. +""" + +import roman5 +import unittest + +class KnownValues(unittest.TestCase): + known_values = ( (1, 'I'), + (2, 'II'), + (3, 'III'), + (4, 'IV'), + (5, 'V'), + (6, 'VI'), + (7, 'VII'), + (8, 'VIII'), + (9, 'IX'), + (10, 'X'), + (50, 'L'), + (100, 'C'), + (500, 'D'), + (1000, 'M'), + (31, 'XXXI'), + (148, 'CXLVIII'), + (294, 'CCXCIV'), + (312, 'CCCXII'), + (421, 'CDXXI'), + (528, 'DXXVIII'), + (621, 'DCXXI'), + (782, 'DCCLXXXII'), + (870, 'DCCCLXX'), + (941, 'CMXLI'), + (1043, 'MXLIII'), + (1110, 'MCX'), + (1226, 'MCCXXVI'), + (1301, 'MCCCI'), + (1485, 'MCDLXXXV'), + (1509, 'MDIX'), + (1607, 'MDCVII'), + (1754, 'MDCCLIV'), + (1832, 'MDCCCXXXII'), + (1993, 'MCMXCIII'), + (2074, 'MMLXXIV'), + (2152, 'MMCLII'), + (2212, 'MMCCXII'), + (2343, 'MMCCCXLIII'), + (2499, 'MMCDXCIX'), + (2574, 'MMDLXXIV'), + (2646, 'MMDCXLVI'), + (2723, 'MMDCCXXIII'), + (2892, 'MMDCCCXCII'), + (2975, 'MMCMLXXV'), + (3051, 'MMMLI'), + (3185, 'MMMCLXXXV'), + (3250, 'MMMCCL'), + (3313, 'MMMCCCXIII'), + (3408, 'MMMCDVIII'), + (3501, 'MMMDI'), + (3610, 'MMMDCX'), + (3743, 'MMMDCCXLIII'), + (3844, 'MMMDCCCXLIV'), + (3888, 'MMMDCCCLXXXVIII'), + (3940, 'MMMCMXL'), + (3999, 'MMMCMXCIX')) + + def test_to_roman_known_values(self): + """to_roman should give known result with known input""" + for integer, numeral in self.known_values: + result = roman5.to_roman(integer) + self.assertEqual(numeral, result) + + def test_from_roman_known_values(self): + """from_roman should give known result with known input""" + for integer, numeral in self.known_values: + result = roman5.from_roman(numeral) + self.assertEqual(integer, result) + +class ToRomanBadInput(unittest.TestCase): + def test_too_large(self): + """to_roman should fail with large input""" + self.assertRaises(roman5.OutOfRangeError, roman5.to_roman, 4000) + + def test_zero(self): + """to_roman should fail with 0 input""" + self.assertRaises(roman5.OutOfRangeError, roman5.to_roman, 0) + + def test_negative(self): + """to_roman should fail with negative input""" + self.assertRaises(roman5.OutOfRangeError, roman5.to_roman, -1) + + def test_non_integer(self): + """to_roman should fail with non-integer input""" + self.assertRaises(roman5.NotIntegerError, roman5.to_roman, 0.5) + +class SanityCheck(unittest.TestCase): + def testSanity(self): + """from_roman(to_roman(n))==n for all n""" + for integer in range(1, 4000): + numeral = roman5.to_roman(integer) + result = roman5.from_roman(numeral) + self.assertEqual(integer, result) + +if __name__ == "__main__": + unittest.main() diff --git a/romantest6.py b/romantest6.py new file mode 100644 index 0000000..ebf535b --- /dev/null +++ b/romantest6.py @@ -0,0 +1,124 @@ +"""Unit test for roman1.py + +This program is part of "Dive Into Python 3", a free Python book for +experienced programmers. Visit http://diveintopython3.org/ for the +latest version. +""" + +import roman6 +import unittest + +class KnownValues(unittest.TestCase): + known_values = ( (1, 'I'), + (2, 'II'), + (3, 'III'), + (4, 'IV'), + (5, 'V'), + (6, 'VI'), + (7, 'VII'), + (8, 'VIII'), + (9, 'IX'), + (10, 'X'), + (50, 'L'), + (100, 'C'), + (500, 'D'), + (1000, 'M'), + (31, 'XXXI'), + (148, 'CXLVIII'), + (294, 'CCXCIV'), + (312, 'CCCXII'), + (421, 'CDXXI'), + (528, 'DXXVIII'), + (621, 'DCXXI'), + (782, 'DCCLXXXII'), + (870, 'DCCCLXX'), + (941, 'CMXLI'), + (1043, 'MXLIII'), + (1110, 'MCX'), + (1226, 'MCCXXVI'), + (1301, 'MCCCI'), + (1485, 'MCDLXXXV'), + (1509, 'MDIX'), + (1607, 'MDCVII'), + (1754, 'MDCCLIV'), + (1832, 'MDCCCXXXII'), + (1993, 'MCMXCIII'), + (2074, 'MMLXXIV'), + (2152, 'MMCLII'), + (2212, 'MMCCXII'), + (2343, 'MMCCCXLIII'), + (2499, 'MMCDXCIX'), + (2574, 'MMDLXXIV'), + (2646, 'MMDCXLVI'), + (2723, 'MMDCCXXIII'), + (2892, 'MMDCCCXCII'), + (2975, 'MMCMLXXV'), + (3051, 'MMMLI'), + (3185, 'MMMCLXXXV'), + (3250, 'MMMCCL'), + (3313, 'MMMCCCXIII'), + (3408, 'MMMCDVIII'), + (3501, 'MMMDI'), + (3610, 'MMMDCX'), + (3743, 'MMMDCCXLIII'), + (3844, 'MMMDCCCXLIV'), + (3888, 'MMMDCCCLXXXVIII'), + (3940, 'MMMCMXL'), + (3999, 'MMMCMXCIX')) + + def test_to_roman_known_values(self): + """to_roman should give known result with known input""" + for integer, numeral in self.known_values: + result = roman6.to_roman(integer) + self.assertEqual(numeral, result) + + def test_from_roman_known_values(self): + """from_roman should give known result with known input""" + for integer, numeral in self.known_values: + result = roman6.from_roman(numeral) + self.assertEqual(integer, result) + +class ToRomanBadInput(unittest.TestCase): + def test_too_large(self): + """to_roman should fail with large input""" + self.assertRaises(roman6.OutOfRangeError, roman6.to_roman, 4000) + + def test_zero(self): + """to_roman should fail with 0 input""" + self.assertRaises(roman6.OutOfRangeError, roman6.to_roman, 0) + + def test_negative(self): + """to_roman should fail with negative input""" + self.assertRaises(roman6.OutOfRangeError, roman6.to_roman, -1) + + def test_non_integer(self): + """to_roman should fail with non-integer input""" + self.assertRaises(roman6.NotIntegerError, roman6.to_roman, 0.5) + +class FromRomanBadInput(unittest.TestCase): + def test_too_many_repeated_numerals(self): + """from_roman should fail with too many repeated numerals""" + for s in ('MMMMM', 'DD', 'CCCC', 'LL', 'XXXX', 'VV', 'IIII'): + self.assertRaises(roman6.InvalidRomanNumeralError, roman6.from_roman, s) + + def test_repeated_pairs(self): + """from_roman should fail with repeated pairs of numerals""" + for s in ('CMCM', 'CDCD', 'XCXC', 'XLXL', 'IXIX', 'IVIV'): + self.assertRaises(roman6.InvalidRomanNumeralError, roman6.from_roman, s) + + def test_malformed_antecedents(self): + """from_roman should fail with malformed antecedents""" + for s in ('IIMXCC', 'VX', 'DCM', 'CMM', 'IXIV', + 'MCMC', 'XCX', 'IVI', 'LM', 'LD', 'LC'): + self.assertRaises(roman6.InvalidRomanNumeralError, roman6.from_roman, s) + +class SanityCheck(unittest.TestCase): + def testSanity(self): + """from_roman(to_roman(n))==n for all n""" + for integer in range(1, 4000): + numeral = roman6.to_roman(integer) + result = roman6.from_roman(numeral) + self.assertEqual(integer, result) + +if __name__ == "__main__": + unittest.main() diff --git a/romantest7.py b/romantest7.py new file mode 100644 index 0000000..3e4a699 --- /dev/null +++ b/romantest7.py @@ -0,0 +1,128 @@ +"""Unit test for roman1.py + +This program is part of "Dive Into Python 3", a free Python book for +experienced programmers. Visit http://diveintopython3.org/ for the +latest version. +""" + +import roman7 +import unittest + +class KnownValues(unittest.TestCase): + known_values = ( (1, 'I'), + (2, 'II'), + (3, 'III'), + (4, 'IV'), + (5, 'V'), + (6, 'VI'), + (7, 'VII'), + (8, 'VIII'), + (9, 'IX'), + (10, 'X'), + (50, 'L'), + (100, 'C'), + (500, 'D'), + (1000, 'M'), + (31, 'XXXI'), + (148, 'CXLVIII'), + (294, 'CCXCIV'), + (312, 'CCCXII'), + (421, 'CDXXI'), + (528, 'DXXVIII'), + (621, 'DCXXI'), + (782, 'DCCLXXXII'), + (870, 'DCCCLXX'), + (941, 'CMXLI'), + (1043, 'MXLIII'), + (1110, 'MCX'), + (1226, 'MCCXXVI'), + (1301, 'MCCCI'), + (1485, 'MCDLXXXV'), + (1509, 'MDIX'), + (1607, 'MDCVII'), + (1754, 'MDCCLIV'), + (1832, 'MDCCCXXXII'), + (1993, 'MCMXCIII'), + (2074, 'MMLXXIV'), + (2152, 'MMCLII'), + (2212, 'MMCCXII'), + (2343, 'MMCCCXLIII'), + (2499, 'MMCDXCIX'), + (2574, 'MMDLXXIV'), + (2646, 'MMDCXLVI'), + (2723, 'MMDCCXXIII'), + (2892, 'MMDCCCXCII'), + (2975, 'MMCMLXXV'), + (3051, 'MMMLI'), + (3185, 'MMMCLXXXV'), + (3250, 'MMMCCL'), + (3313, 'MMMCCCXIII'), + (3408, 'MMMCDVIII'), + (3501, 'MMMDI'), + (3610, 'MMMDCX'), + (3743, 'MMMDCCXLIII'), + (3844, 'MMMDCCCXLIV'), + (3888, 'MMMDCCCLXXXVIII'), + (3940, 'MMMCMXL'), + (3999, 'MMMCMXCIX')) + + def test_to_roman_known_values(self): + """to_roman should give known result with known input""" + for integer, numeral in self.known_values: + result = roman7.to_roman(integer) + self.assertEqual(numeral, result) + + def test_from_roman_known_values(self): + """from_roman should give known result with known input""" + for integer, numeral in self.known_values: + result = roman7.from_roman(numeral) + self.assertEqual(integer, result) + +class ToRomanBadInput(unittest.TestCase): + def test_too_large(self): + """to_roman should fail with large input""" + self.assertRaises(roman7.OutOfRangeError, roman7.to_roman, 4000) + + def test_zero(self): + """to_roman should fail with 0 input""" + self.assertRaises(roman7.OutOfRangeError, roman7.to_roman, 0) + + def test_negative(self): + """to_roman should fail with negative input""" + self.assertRaises(roman7.OutOfRangeError, roman7.to_roman, -1) + + def test_non_integer(self): + """to_roman should fail with non-integer input""" + self.assertRaises(roman7.NotIntegerError, roman7.to_roman, 0.5) + +class FromRomanBadInput(unittest.TestCase): + def test_too_many_repeated_numerals(self): + """from_roman should fail with too many repeated numerals""" + for s in ('MMMMM', 'DD', 'CCCC', 'LL', 'XXXX', 'VV', 'IIII'): + self.assertRaises(roman7.InvalidRomanNumeralError, roman7.from_roman, s) + + def test_repeated_pairs(self): + """from_roman should fail with repeated pairs of numerals""" + for s in ('CMCM', 'CDCD', 'XCXC', 'XLXL', 'IXIX', 'IVIV'): + self.assertRaises(roman7.InvalidRomanNumeralError, roman7.from_roman, s) + + def test_malformed_antecedents(self): + """from_roman should fail with malformed antecedents""" + for s in ('IIMXCC', 'VX', 'DCM', 'CMM', 'IXIV', + 'MCMC', 'XCX', 'IVI', 'LM', 'LD', 'LC'): + self.assertRaises(roman7.InvalidRomanNumeralError, roman7.from_roman, s) + + def test_blank(self): + """from_roman should fail with blank string""" + self.assertRaises(roman7.InvalidRomanNumeralError, roman7.from_roman, "") + +class SanityCheck(unittest.TestCase): + def testSanity(self): + """from_roman(to_roman(n))==n for all n""" + for integer in range(1, 4000): + numeral = roman7.to_roman(integer) + result = roman7.from_roman(numeral) + self.assertEqual(integer, result) + +if __name__ == "__main__": + unittest.main() diff --git a/romantest8.py b/romantest8.py new file mode 100644 index 0000000..cf9c421 --- /dev/null +++ b/romantest8.py @@ -0,0 +1,132 @@ +"""Unit test for roman1.py + +This program is part of "Dive Into Python 3", a free Python book for +experienced programmers. Visit http://diveintopython3.org/ for the +latest version. +""" + +import roman8 +import unittest + +class KnownValues(unittest.TestCase): + known_values = ( (1, 'I'), + (2, 'II'), + (3, 'III'), + (4, 'IV'), + (5, 'V'), + (6, 'VI'), + (7, 'VII'), + (8, 'VIII'), + (9, 'IX'), + (10, 'X'), + (50, 'L'), + (100, 'C'), + (500, 'D'), + (1000, 'M'), + (31, 'XXXI'), + (148, 'CXLVIII'), + (294, 'CCXCIV'), + (312, 'CCCXII'), + (421, 'CDXXI'), + (528, 'DXXVIII'), + (621, 'DCXXI'), + (782, 'DCCLXXXII'), + (870, 'DCCCLXX'), + (941, 'CMXLI'), + (1043, 'MXLIII'), + (1110, 'MCX'), + (1226, 'MCCXXVI'), + (1301, 'MCCCI'), + (1485, 'MCDLXXXV'), + (1509, 'MDIX'), + (1607, 'MDCVII'), + (1754, 'MDCCLIV'), + (1832, 'MDCCCXXXII'), + (1993, 'MCMXCIII'), + (2074, 'MMLXXIV'), + (2152, 'MMCLII'), + (2212, 'MMCCXII'), + (2343, 'MMCCCXLIII'), + (2499, 'MMCDXCIX'), + (2574, 'MMDLXXIV'), + (2646, 'MMDCXLVI'), + (2723, 'MMDCCXXIII'), + (2892, 'MMDCCCXCII'), + (2975, 'MMCMLXXV'), + (3051, 'MMMLI'), + (3185, 'MMMCLXXXV'), + (3250, 'MMMCCL'), + (3313, 'MMMCCCXIII'), + (3408, 'MMMCDVIII'), + (3501, 'MMMDI'), + (3610, 'MMMDCX'), + (3743, 'MMMDCCXLIII'), + (3844, 'MMMDCCCXLIV'), + (3888, 'MMMDCCCLXXXVIII'), + (3940, 'MMMCMXL'), + (3999, 'MMMCMXCIX')) + + def test_to_roman_known_values(self): + """to_roman should give known result with known input""" + for integer, numeral in self.known_values: + result = roman8.to_roman(integer) + self.assertEqual(numeral, result) + + def test_from_roman_known_values(self): + """from_roman should give known result with known input""" + for integer, numeral in self.known_values: + result = roman8.from_roman(numeral) + self.assertEqual(integer, result) + +class ToRomanBadInput(unittest.TestCase): + def test_too_large(self): + """to_roman should fail with large input""" + self.assertRaises(roman8.OutOfRangeError, roman8.to_roman, 4000) + + def test_zero(self): + """to_roman should fail with 0 input""" + self.assertRaises(roman8.OutOfRangeError, roman8.to_roman, 0) + + def test_negative(self): + """to_roman should fail with negative input""" + self.assertRaises(roman8.OutOfRangeError, roman8.to_roman, -1) + + def test_non_integer(self): + """to_roman should fail with non-integer input""" + self.assertRaises(roman8.NotIntegerError, roman8.to_roman, 0.5) + +class FromRomanBadInput(unittest.TestCase): + def test_too_many_repeated_numerals(self): + """from_roman should fail with too many repeated numerals""" + for s in ('MMMMM', 'DD', 'CCCC', 'LL', 'XXXX', 'VV', 'IIII'): + self.assertRaises(roman8.InvalidRomanNumeralError, roman8.from_roman, s) + + def test_repeated_pairs(self): + """from_roman should fail with repeated pairs of numerals""" + for s in ('CMCM', 'CDCD', 'XCXC', 'XLXL', 'IXIX', 'IVIV'): + self.assertRaises(roman8.InvalidRomanNumeralError, roman8.from_roman, s) + + def test_malformed_antecedents(self): + """from_roman should fail with malformed antecedents""" + for s in ('IIMXCC', 'VX', 'DCM', 'CMM', 'IXIV', + 'MCMC', 'XCX', 'IVI', 'LM', 'LD', 'LC'): + self.assertRaises(roman8.InvalidRomanNumeralError, roman8.from_roman, s) + + def test_blank(self): + """from_roman should fail with blank string""" + self.assertRaises(roman8.InvalidRomanNumeralError, roman8.from_roman, "") + + def test_non_string(self): + """from_roman should fail with non-string input""" + self.assertRaises(roman8.InvalidRomanNumeralError, roman8.from_roman, 1) + +class SanityCheck(unittest.TestCase): + def testSanity(self): + """from_roman(to_roman(n))==n for all n""" + for integer in range(1, 4000): + numeral = roman8.to_roman(integer) + result = roman8.from_roman(numeral) + self.assertEqual(integer, result) + +if __name__ == "__main__": + unittest.main()