Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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

This comment has been minimized.

Copy link

commented Feb 11, 2018

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

This comment has been minimized.

Copy link

commented Feb 11, 2018

@Willem3141

This comment has been minimized.

Copy link

commented Dec 29, 2018

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.

@Linekio

This comment has been minimized.

Copy link

commented Feb 15, 2019

Just need n * "I" and replace:

SUBS = [
    ("IIIII", "V"),
    ("VV", "X"),
    ("XXXXX", "L"),
    ("LL", "C"),
    ("CCCCC", "D"),
    ("DD", "M"),
    ("IIII", "IV"),
    ("VIV", "IX"),
    ("XXXX", "XL"),
    ("LXL", "XC"),
    ("CCCC", "CD"),
    ("DCD", "CM"),
]


def to_roman(n):
    res = "I" * n
    for r in SUBS:
        res = res.replace(*r)
    return res


def to_int(n):
    res = n
    for r in SUBS[::-1]:
        res = res.replace(*r[::-1])
    return len(res)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.