Skip to content

Instantly share code, notes, and snippets.

@riverrun
Created August 17, 2015 09:13
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save riverrun/ac91218bb1678b857c12 to your computer and use it in GitHub Desktop.
Save riverrun/ac91218bb1678b857c12 to your computer and use it in GitHub Desktop.
Convert to and from roman numerals in python
#!/usr/bin/python3
import unittest
class ToRoman(int):
def __new__(cls, number):
if number > 3999:
raise ValueError('Values over 3999 are not allowed: {}'.format(number))
if number < 0:
raise ValueError('Negative values are not allowed: {}'.format(number))
return super().__new__(cls, number)
def __init__(self, number):
to_roman = {1: 'I', 2: 'II', 3: 'III', 4: 'IV', 5: 'V',
6: 'VI', 7: 'VII', 8: 'VIII', 9: 'IX', 10: 'X', 20: 'XX',
30: 'XXX', 40: 'XL', 50: 'L', 60: 'LX', 70: 'LXX', 80: 'LXXX',
90: 'XC', 100: 'C', 200: 'CC', 300: 'CCC', 400: 'CD', 500: 'D',
600: 'DC', 700: 'DCC', 800: 'DCCC', 900: 'CM', 1000: 'M',
2000: 'MM', 3000: 'MMM'}
self.roman = ''.join([to_roman.get(num) for num in self][::-1])
def __iter__(self):
number = self.__str__()
count = 1
for digit in number[::-1]:
if digit != '0':
yield int(digit) * count
count *= 10
class ToArabic(str):
def __init__(self, roman):
roman = self.check_valid(roman)
keys = ['IV', 'IX', 'XL', 'XC', 'CD', 'CM', 'I', 'V', 'X', 'L', 'C', 'D', 'M']
to_arabic = {'IV': '4', 'IX': '9', 'XL': '40', 'XC': '90', 'CD': '400', 'CM': '900',
'I': '1', 'V': '5', 'X': '10', 'L': '50', 'C': '100', 'D': '500', 'M': '1000'}
for key in keys:
if key in roman:
roman = roman.replace(key, ' {}'.format(to_arabic.get(key)))
self.arabic = sum(int(num) for num in roman.split())
def check_valid(self, roman):
roman = roman.upper()
invalid = ['IIII', 'VV', 'XXXX', 'LL', 'CCCC', 'DD', 'MMMM']
if any(sub in roman for sub in invalid):
raise ValueError('Numerus invalidus est: {}'.format(roman))
return roman
def convert(number):
if isinstance(number, int):
num = ToRoman(number)
return num.roman
num = ToArabic(number)
return num.arabic
class TestRomanConversion(unittest.TestCase):
def setUp(self):
self.numbers = [(1, 'I'), (3, 'III'), (4, 'IV'), (27, 'XXVII'), (44, 'XLIV'),
(93, 'XCIII'), (141, 'CXLI'), (402, 'CDII'), (575, 'DLXXV'),
(1024, 'MXXIV'), (3000, 'MMM')]
def test_to_roman(self):
for num in self.numbers:
self.assertEqual(num[0], convert(num[1]))
def test_to_arabic(self):
for num in self.numbers:
self.assertEqual(num[1], convert(num[0]))
if __name__ == '__main__':
unittest.main()
@rockcesar
Copy link

What if I want to represent MMMMM, 5000 in arabic. That number does exists in Roman. The only that can be represented with more than 3 repetitions is M, 1000.

@rockcesar
Copy link

@Willem3141
Copy link

Random note: this chokes on invalid numbers like IC (returns 101 instead of expected 99). Assuming we actually want to reject this input, this can easily be fixed by checking afterwards if the Arabic number, if converted back to Roman, yields the input again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment