I wrote as an exercise in Test-Driven Development a piece of Python code that contains two functions:
roman2dec(roman)
, that converts a roman number (string) into a decimal number (int)dec2roman(dec)
, that converts a decimal number (int) into a roman number (string)
I'd like to read your comments on this code, if there are bad practices, if you would sign it for shipping or if you'd change anything.
import re
import math
# Regular expression used to validate and parse Roman numbers
roman_re = re.compile("""^
([M]{0,9}) # thousands
([DCM]*) # hundreds
([XLC]*) # tens
([IVX]*) # units
$""", re.VERBOSE)
# This array contains valid groups of digits and encodes their values.
# The first row is for units, the second for tens and the third for
# hundreds. For example, the sixth element of the tens row yields the
# value 50, as the first is 0.
d2r_table = [
['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'],
['', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'],
['', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM']]
def roman2dec(roman):
"""Converts a roman number, encoded in a string, to a decimal number."""
roman = roman.upper()
match = roman_re.match(roman)
if not match:
raise ValueError
thousands, hundreds, tens, units = match.groups()
result = 1000 * len(thousands)
result += d2r_table[2].index(hundreds) * 100
result += d2r_table[1].index(tens) * 10
result += d2r_table[0].index(units)
return result
def dec2roman(dec):
"""Converts a positive decimal integer to a roman number."""
if dec == 0:
return ''
digit = 0
rem = dec
result = ''
# Length in digits of the number dec
dec_len = int(math.ceil(math.log10(dec)) + 1)
# Scan the number digit-by-digit, starting from the MSD (most-significant
# digit)
while dec_len > 0:
# Let's take the current digit
factor = 10 ** (dec_len - 1)
digit = rem / factor
# And remove it from the number
rem = rem - digit * factor
if dec_len >= 4:
# Thousands
result = result + digit * 'M'
else:
# Look in the look-up table
result = result + d2r_table[dec_len - 1][digit]
dec_len -= 1
return result
EDIT: Here is the test suite for dec2roman:
class DecToRoman(unittest.TestCase):
def testZeroIsEmpty(self):
self.checkString(0, "")
def testSingleDigits(self):
self.checkString(1, "I")
self.checkString(10, "X")
self.checkString(50, "L")
self.checkString(100, "C")
self.checkString(500, "D")
self.checkString(1000, "M")
def testSimpleRepeats(self):
self.checkString(1, "I")
self.checkString(2, "II")
self.checkString(3, "III")
self.checkString(10, "X")
self.checkString(20, "XX")
self.checkString(30, "XXX")
def testSubtraction(self):
self.checkString(4, "IV")
self.checkString(9, "IX")
self.checkString(40, "XL")
self.checkString(90, "XC")
def testOther(self):
self.checkString(89, "LXXXIX")
self.checkString(145, "CXLV")
self.checkString(691, "DCXCI")
self.checkString(1983, "MCMLXXXIII")
self.checkString(2412, "MMCDXII")
self.checkString(3309, "MMMCCCIX")
def checkString(self, decimal, expected_string):
self.assertEqual(expected_string, new_dec2roman(decimal))
import roman
;) but it's just an exercise! \$\endgroup\$