20

I'm trying to write a code that converts a user-inputted integer into its Roman numeral equivalent. What I have so far is:

screenshot of my code

The point of the generate_all_of_numeral function is so that it creates a string for each specific numeral. For example, generate_all_of_numeral(2400, 'M', 2000) would return the string 'MM'.

I'm struggling with the main program. I start off finding the Roman numeral count for M and saving that into the variable M. Then I subtract by the number of M's times the symbol value to give me the next value to work with for the next largest numeral.

Any nod to the right direction? Right now my code doesn't even print anything.

1
  • 5
    "What you have so far" is an image, and so it is useless to copy and see what goes wrong.
    – Jongware
    Commented Feb 23, 2018 at 17:17

36 Answers 36

51

One of the best ways to deal with this is using the divmod function. You check if the given number matches any Roman numeral from the highest to the lowest. At every match, you should return the respective character.

Some numbers will have remainders when you use the modulo function, so you also apply the same logic to the remainder. Obviously, I'm hinting at recursion.

See my answer below. I use an OrderedDict to make sure that I can iterate "downwards" the list, then I use a recursion of divmod to generate matches. Finally, I join all generated answers to produce a string.

from collections import OrderedDict

def write_roman(num):

    roman = OrderedDict()
    roman[1000] = "M"
    roman[900] = "CM"
    roman[500] = "D"
    roman[400] = "CD"
    roman[100] = "C"
    roman[90] = "XC"
    roman[50] = "L"
    roman[40] = "XL"
    roman[10] = "X"
    roman[9] = "IX"
    roman[5] = "V"
    roman[4] = "IV"
    roman[1] = "I"

    def roman_num(num):
        for r in roman.keys():
            x, y = divmod(num, r)
            yield roman[r] * x
            num -= (r * x)
            if num <= 0:
                break

    return "".join([a for a in roman_num(num)])

Taking it for a spin:

num = 35
print write_roman(num)
# XXXV

num = 994
print write_roman(num)
# CMXCIV

num = 1995
print write_roman(num)
# MCMXCV

num = 2015
print write_roman(num)
# MMXV
6
  • I have a question, why do you execute roman_num function inside the roman_num function if it doesn't do anything (you don't assign it to anything, so code would work just the same without it)?
    – Jackenmen
    Commented Jan 20, 2019 at 13:48
  • @jack1142 What do you mean? It's a recursive function, is it not? Been a while since I answered this, but I believe that was my logic way back then (not the best, but it's not unsound).
    – WGS
    Commented Jan 20, 2019 at 16:04
  • I mean, look at the code and think what exactly achieves executing roman_num - theoretically you wanted recursion, but it doesn't do anything as you don't yield or put it in some variable for example. All the code that is needed is that loop, execution of roman_num inside the function does nothing in this case.
    – Jackenmen
    Commented Jan 20, 2019 at 16:22
  • 1
    I'm not talking about the yield roman[r] * x line as this one is okay, but think what exactly roman_num execution in if does to your result - it does absolutely nothing (also, it's not even really executed, because it's a generator and it's "paused" until you iterate on it, but that doesn't really matter here). All I'm saying that this line can be removed and program will work the same, because that line doesn't actually do anything to the result.
    – Jackenmen
    Commented Jan 20, 2019 at 17:12
  • 2
    @jack1142 Aha. Seems like you are in the right. Looks like a superfluous line then. Too bad I can't edit it out of integrity. Seen better solutions to this below. Though I suppose I can just edit out the offending line. Thanks!
    – WGS
    Commented Jan 20, 2019 at 17:24
38

Here is another way, without division:

num_map = [(1000, 'M'), (900, 'CM'), (500, 'D'), (400, 'CD'), (100, 'C'), (90, 'XC'),
           (50, 'L'), (40, 'XL'), (10, 'X'), (9, 'IX'), (5, 'V'), (4, 'IV'), (1, 'I')]


def num2roman(num):

    roman = ''

    while num > 0:
        for i, r in num_map:
            while num >= i:
                roman += r
                num -= i

    return roman

# test 
>>> num2roman(2242)
'MMCCXLII'

Update see the execution visualized

2
  • mind blown! just unsure about for i, r in num_map: meaning
    – 3kstc
    Commented Mar 22, 2017 at 2:53
  • 1
    oh hope that could be helpful :-) for i, r in num_map is to loop over each (numeral, roman) pair until it finds first numeral that is leq current input number. See this visualized code: goo.gl/iZPAXO maybe better than my words.
    – Aziz Alto
    Commented Mar 22, 2017 at 3:32
24

A KISS version of Manhattan's algorithm, without any "advanced" notion such as OrderedDict, recursion, generators, inner function and break:

ROMAN = [
    (1000, "M"),
    ( 900, "CM"),
    ( 500, "D"),
    ( 400, "CD"),
    ( 100, "C"),
    (  90, "XC"),
    (  50, "L"),
    (  40, "XL"),
    (  10, "X"),
    (   9, "IX"),
    (   5, "V"),
    (   4, "IV"),
    (   1, "I"),
]

def int_to_roman(number):
    result = ""
    for (arabic, roman) in ROMAN:
        (factor, number) = divmod(number, arabic)
        result += roman * factor
    return result

An early exit could be added as soon as number reaches zero, and the string accumulation could be made more pythonic, but my goal here was to produce the requested basic program.

Tested on all integers from 1 to 100000, which ought to be enough for anybody.

EDIT: the slightly more pythonic and faster version I alluded to:

def int_to_roman(number):
    result = []
    for (arabic, roman) in ROMAN:
        (factor, number) = divmod(number, arabic)
        result.append(roman * factor)
        if number == 0:
            break
    return "".join(result)
1
  • 1
    Most elegant. Why not just show the improved implementation? Commented Sep 4, 2018 at 19:13
6

Here's a lambda function for integer to roman numeral conversion, working up to 3999. It anchors some corner of the space of "unreadable things you probably don't actually want to do". But it may amuse someone:

lambda a: (
    "".join(reversed([
      "".join([
          "IVXLCDM"[int(d)+i*2]
          for d in [
              "", "0", "00", "000", "01",
              "1", "10", "100", "1000", "02"][int(c)]])
          for i,c in enumerate(reversed(str(a))) ]))
     )

This approach gives an alternative to using arithmetical manipulations to isolate decimal digits and their place, as OP and many of the examples do. The approach here goes straight for converting the decimal number to a string. That way, digits can be isolated by list indexing. The data table is fairly compressed, and no subtraction or division is used.

Admittedly, in the form given, whatever is gained in brevity is immediately given up in readability. For people without time for puzzles, a version below is given that avoids list comprehension and lambda functions.

Stepthrough

But I'll explain the lambda function version here...

Going from back to front:

  1. Convert a decimal integer to a reversed string of its digits, and enumerate (i) over the reversed digits (c).

    ....
    for i,c in enumerate(reversed(str(a)))
    ....
    
  2. Convert each digit c back to an integer (range of 0-9), and use it as an index into a list of magic digit strings. The magic is explained a little later on.

    ....
    [ "", "0", "00", "000", "01",
     "1", "10", "100", "1000", "02"][int(c)]])
    ....
    
  3. Convert your selected magic digit string into a string of roman numeral "digits". Basically, you now have your decimal digit expressed as roman numeral digits appropriate to the original 10's place of the decimal digit. This is the target of the generate_all_of_numeral function used by the OP.

    ....
    "".join([
        "IVXLCDM"[int(d)+i*2]
        for d in <magic digit string>
    ....
    
  4. Concatenate everything back in reversed order. The reversal is of the order of the digits, but order within the digits ("digits"?) is unaffected.

    lambda a: (
        "".join(reversed([
        <roman-numeral converted digits>
        ]))
    

The Magic String List

Now, about that list of magic strings. It allows selecting the appropriate string of roman numeral digits (up to four of them, each being one of three types 0, 1, or 2) for each different 10's place that a decimal digit can occupy.

  • 0 -> ""; roman numerals don't show zeros.
  • 1 -> "0"; 0 + 2*i maps to I, X, C or M -> I, X, C or M.
  • 2 -> "00"; like for 1, x2 -> II, XX, CC, MM.
  • 3 -> "000"; like for 1, x3 -> III, XXX, CCC, MMM.
  • 4 -> "01"; like for 1, then 1 +2*i maps to V, L, or D -> IV, XL, CD.
  • 5 -> "1"; maps to odd roman numeral digits -> V, L, D.
  • 6 -> "10"; reverse of 4 -> VI, LX, DC.
  • 7 -> "100"; add another I/X/C -> VII LXX, DCC
  • 8 -> "1000"; add another I/X/C -> VIII, LXXX, DCCC
  • 9 -> "02"; like for 1, plus the next 10's level up (2 + i*2) -> IX, XC, CM.

At 4000 and above, this will throw an exception. "MMMM" = 4000, but this doesn't match the pattern anymore, breaking the assumptions of the algorithm.

Rewritten version

...as promised above...

def int_to_roman(a):
    all_roman_digits = []
    digit_lookup_table = [
        "", "0", "00", "000", "01",
        "1", "10", "100", "1000", "02"]
    for i,c in enumerate(reversed(str(a))):
        roman_digit = ""
        for d in digit_lookup_table[int(c)]:
          roman_digit += ("IVXLCDM"[int(d)+i*2])
        all_roman_digits.append(roman_digit)
    return "".join(reversed(all_roman_digits))

I again left out exception trapping, but at least now there's a place to put it inline.

6

I referred to this url for online decimal to roman conversion. If we extend the range of decimals up to 3,999,999 the script given by @Manhattan will not work. Here is the the correct script up to the range of 3,999,999.

def int_to_roman(num):
    _values = [
        1000000, 900000, 500000, 400000, 100000, 90000, 50000, 40000, 10000, 9000, 5000, 4000, 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]

    _strings = [
        'M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', "M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]

    result = ""
    decimal = num

    while decimal > 0:
        for i in range(len(_values)):
            if decimal >= _values[i]:
                if _values[i] > 1000:
                    result += u'\u0304'.join(list(_strings[i])) + u'\u0304'
                else:
                    result += _strings[i]
                decimal -= _values[i]
                break
    return result

The unicode character u'\0304' prints the overline char; e.g. enter image description here

Sample Output:

enter image description here

4
  • This is fantastic and also covers Romans chars with BAR above. This is the perfect answer
    – Ronu
    Commented May 7, 2018 at 19:22
  • Excellent answer, +1. I changed the comma in the highest number to 3,999,999 which is what I think you wanted. Change it back if I misunderstood. Commented Jan 30, 2019 at 0:45
  • 2
    input of 900000 gives output of C̅ ... but the correct output should be C̅M̅
    – dnk8n
    Commented Jan 7, 2020 at 8:29
  • 2
    Also note, I found that u'\u0305' gave a more satisfactory overline
    – dnk8n
    Commented Jan 7, 2020 at 8:31
6

The Python package roman (repository) can be used to convert to and from roman numerals:

import roman

r = roman.toRoman(5)
assert r == 'V', r
n = roman.fromRoman('V')
assert n == 5, n

This package can be installed from the Python Package Index (PyPI) using the package manager pip:

pip install roman
2

The approach by Laughing Man works. Using an ordered dictionary is clever. But his code re-creates the ordered dictionary every time the function is called, and within the function, in every recursive call, the function steps through the whole ordered dictionary from the top. Also, divmod returns both the quotient and the remainder, but the remainder is not used. A more direct approach is as follows.

def _getRomanDictOrdered():
    #
    from collections import OrderedDict
    #
    dIntRoman = OrderedDict()
    #
    dIntRoman[1000] = "M"
    dIntRoman[900] = "CM"
    dIntRoman[500] = "D"
    dIntRoman[400] = "CD"
    dIntRoman[100] = "C"
    dIntRoman[90] = "XC"
    dIntRoman[50] = "L"
    dIntRoman[40] = "XL"
    dIntRoman[10] = "X"
    dIntRoman[9] = "IX"
    dIntRoman[5] = "V"
    dIntRoman[4] = "IV"
    dIntRoman[1] = "I"
    #
    return dIntRoman

_dIntRomanOrder = _getRomanDictOrdered() # called once on import

def getRomanNumeralOffInt( iNum ):
    #
    lRomanNumerals = []
    #
    for iKey in _dIntRomanOrder:
        #
        if iKey > iNum: continue
        #
        iQuotient = iNum // iKey
        #
        if not iQuotient: continue
        #
        lRomanNumerals.append( _dIntRomanOrder[ iKey ] * iQuotient )
        #
        iNum -= ( iKey * iQuotient )
        #
        if not iNum: break
        #
    #
    return ''.join( lRomanNumerals )

Checking the results:

>>> getRomanNumeralOffInt(35)
'XXXV'
>>> getRomanNumeralOffInt(994)
'CMXCIV'
>>> getRomanNumeralOffInt(1995)
'MCMXCV'
>>> getRomanNumeralOffInt(2015)
'MMXV'
2

I have observed that in most of the answers, people are storing excess notations like "IX" for 9, "XL" for 40 and so on.

This misses the main essence of Roman Conversion.

Here's a small introduction and algorithm before I actually paste the code.

The original pattern for Roman numerals used the symbols I, V. and X (1, 5, and 10) as simple tally marks. Each marker for 1 (I) added a unit value up to 5 (V), and was then added to (V) to make the numbers from 6 to 9:

I, II, III, IIII, V, VI, VII, VIII, VIIII, X.

The numerals for 4 (IIII) and 9 (VIIII) proved problematic, and are generally replaced with IV (one less than 5) and IX (one less than 10). This feature of Roman numerals is called subtractive notation.

The numbers from 1 to 10 (including subtractive notation for 4 and 9) are expressed in Roman numerals as follows:

I, II, III, IV, V, VI, VII, VIII, IX, X.

The system being basically decimal, tens and hundreds follow the same pattern: Thus 10 to 100 (counting in tens, with X taking the place of I, L taking the place of V and C taking the place of X):

X, XX, XXX, XL, L, LX, LXX, LXXX, XC, C. Roman Numerals - Wikipedia

So, the main logic that can derive from the above introduction is that, we would strip the positional value and perform divisions based on the values of the literals Romans used.

Let's start the base example. We have the integral list of the literals as [10, 5, 1]

  1. 1/10 = 0.1 (not of much use)

    1/5 = 0.2 (not of much use, either)

    1/1 = 1.0 (hmm, we got something!)

    CASE 1: So, if quotient == 1, print the literal corresponding to the integer. So, the best data structure would be a dictionary. {10: "X", 5: "V", 1:"I"}

    "I" will be printed.

  2. 2/10 = 0.2

    2/5 = 0.4

    2/1 = 2

    CASE 2: So, if quotient > 1, print the literal corresponding to the integer which made it so and subtract it from the number. This makes it 1 and it falls to CASE 1. "II" is printed.

  3. 3/10 = 0.3

    3/5 = 0.6

    3/1 = 3

    So, CASE 2: "I", CASE 2: "II" and CASE 1: "III"

  4. CASE 3: Add 1 and check if quotient == 1.

    (4+1)/10 = 0.5

    (4+1)/5 = 1

    So, this is the case where we first subtract the divisor and the number and print the literal corresponding to the result, followed by the divisor. 5-4=1, thus "IV" will be printed.

  5. (9+1)/10 == 1

    10-9=1. Print "I", print "X", i.e. "IX"

This extends to the tenths place and hundredths as well.

  1. (90+(10^1))/100 = 1.

    Print 100-90="X", followed by 100="C".

  2. (400+(10^2))/500 = 1.

    Print 500-400="C", followed by 500="D".

The last thing we need here is, extract the positional values. Ex: 449 should yield 400, 40, 9.

This can be made by removing the subtracting the modulo of 10^(position-1) and then taking the modulo of 10^position.

Ex: 449, position = 2: 449%(10^1) = 9 -> 449-9 -> 440%(10^2)= 40.

'''
Created on Nov 20, 2017

@author: lu5er
'''
n = int(input())
ls = [1000, 500, 100, 50, 10, 5, 1]
st = {1000:"M", 500:"D", 100:"C", 50:"L", 10:"X", 5:"V", 1:"I"}

rem = 0

# We traverse the number from right to left, extracting the position
for i in range(len(str(n)), 0, -1):
    pos = i # stores the current position
    num = (n-n%(10**(pos-1)))%(10**pos) # extracts the positional values
    while(num>0):
        for div in ls:

            # CASE 1: Logic for 1, 5 and 10
            if num/div == 1:
                #print("here")
                print(st[div], end="")
                num-=div
                break

            # CASE 2: logic for 2, 3, 6 and 8
            if num/div > 1:
                print(st[div],end="")
                num-=div
                break

            # CASE 3: Logic for 4 and 9
            if (num+(10**(pos-1)))/div == 1:
                print(st[div-num], end="")
                print(st[div], end="")
                num-=div
                break

Output Test

99
XCIX

499
CDXCIX

1954
MCMLIV

1990
MCMXC

2014
MMXIV

35
XXXV

994
CMXCIV
1

I was working through this conversion as a kata exercise, and I came up with a solution that takes advantage of Python's string operations:

from collections import namedtuple

Abbreviation = namedtuple('Abbreviation', 'long short')
abbreviations = [
    Abbreviation('I' * 1000, 'M'),
    Abbreviation('I' * 500, 'D'),
    Abbreviation('I' * 100, 'C'),
    Abbreviation('I' * 50, 'L'),
    Abbreviation('I' * 10, 'X'),
    Abbreviation('I' * 5, 'V'),
    Abbreviation('DCCCC', 'CM'),
    Abbreviation('CCCC', 'CD'),
    Abbreviation('LXXXX', 'XC'),
    Abbreviation('XXXX', 'XL'),
    Abbreviation('VIIII', 'IX'),
    Abbreviation('IIII', 'IV')
]


def to_roman(arabic):
    roman = 'I' * arabic
    for abbr in abbreviations:
        roman = roman.replace(abbr.long, abbr.short)

    return roman

I like its simplicity! No need for modulo operations, conditionals, or multiple loops. Of course, you don't need namedtuples either; you can use plain tuples or lists instead.

1

This is my approach

def itr(num):  
    dct = { 1: "I", 4: "IV", 5: "V", 9: "IX", 10: "X", 40: "XL", 50: "L", 90: "XC", 100: "C", 400: "CD", 500: "D", 900: "CM", 1000: "M" }
    if(num in dct):
        return dct[num]

    for i in [1000,100,10,1]:
        for j in [9*i, 5*i, 4*i, i]:
            if(num>=j):
                return itr(j) + itr(num-j) 
1

Interesting question. Several approaches but haven't seen this one yet. We can trade execution time for memory, and reduce potential for pesky arithmetic errors (off-by-one, int vs float div, etc).

Each arabic digit (1s, 10s, 100s, etc) translates to a unique Roman sequence. So just use a lookup table.

ROMAN = {
    0 : ['', 'm', 'mm', 'mmm'],
    1 : ['', 'c', 'cc', 'ccc', 'cd', 'd', 'dc', 'dcc', 'dccc', 'cm'],
    2 : ['', 'x', 'xx', 'xxx', 'xl', 'l', 'lx', 'lxx', 'lxxx', 'xc'],
    3 : ['', 'i', 'ii', 'iii', 'iv', 'v', 'vi', 'vii', 'viii', 'ix'],
}

def to_roman (num, lower = True):
    '''Return the roman numeral version of num'''
    ret = ''
    digits = '%04d' % num
    for pos, digit in enumerate (digits):
        ret += ROMAN [pos] [int (digit)]
    return lower and ret or ret.upper ()

You can add checks for 0 > num > 4000, but not necessary as lookup will fail on int conversion for '-' or index out of range. I prefer lowercase, but upper available as well.

Digit selection could be done arithmetically, but algorithm becomes a bit trickier. Lookups are simple and effective.

1

Here's what I tried:

def int_to_roman(num):
    dict_of_known_val = {1000: 'M', 900: 'CM', 500: 'D', 400: 'CD', 100: 'C',
                         90: 'XC', 50: 'L', 40: 'XL', 10: 'X', 9: 'IX', 5: 'V', 4: 'IV', 1: 'I'}
    result = ''
    for key, value in dict_of_known_val.items():
        x, y = divmod(num, key)
        result += value * x
        num = y
    return result

This code works for me.

3
  • And does it work? Is this actually an answer to the question?
    – joanis
    Commented Mar 18, 2022 at 16:28
  • It seems to work. Just please put the indentation after line 1. When I pasted my code here the next line didnt get indented. Kindly bear with me. I am a new coder and its my first answer here. Please fix the indentation and run the code. Commented Mar 18, 2022 at 19:36
  • I just fixed your indentation. When you paste code into SO, select all of it when you're done and click the {} button to have it formatted correctly as a code block.
    – joanis
    Commented Mar 18, 2022 at 20:56
0

You have to make the symbolCount a global variable. And use () in print method.

0

Only 1 - 999

while True:

 num = input()
 def val(n):
    if n == 1:
        rom = 'I'
        return rom
    if n == 4:
        rom = 'IV'
        return rom
    if n == 5:
        rom = 'V'
        return rom
    if n == 9:
        rom = 'IX'
        return rom
    if n == 10:
        rom = 'X'
        return rom
    if n == 40:
        rom = 'XL'
        return rom
    if n == 50:
        rom = 'L'
        return rom
    if n == 90:
        rom = 'XC'
        return rom
    if n == 100:
        rom = 'C'
        return rom
    if n == 400:
        rom = 'CD'
        return rom
    if n == 500:
        rom = 'D'
        return rom
    if n == 900:
        rom = 'CM'
        return rom

 def lastdigit(num02):
    num02 = num % 10
    num03 = num % 5
    if 9 > num02 > 5:
        return str('V' + 'I'*num03)
    elif num02 < 4:
        return str('I'*num03)
    else:
        return str(val(num02))

 k3 = lastdigit(num)

 def tensdigit(num12):
    num12 = num % 100 - num % 10
    num13 = num % 50
    if 90 > num12 > 50:
        return str('L' + 'X'*(num13/10))
    elif num12 < 40:
        return str('X'*(num13/10))
    else:
        return str(val(num12))

 k2 = tensdigit(num)

 def hundigit(num112):
    num112 = (num % 1000 - num % 100)
    num113 = num % 500
    if 900 > num112 > 500:
        return str('D' + 'C'*(num113/100))
    elif num112 < 400:
        return str('C'*(num113/100))
    else:
        return str(val(num112))

 k1 = hundigit(num)

 print '%s%s%s' %(k1,k2,k3)
0

Another way to do this. separating out processing of number starting with 4 , 9 and others. it can be simplified further

def checkio(data):
romans = [("I",1),("V",5),("X",10),("L",50),("C",100),("D",500),("M",1000)]
romans_rev = list(sorted(romans,key = lambda x: -x[1]))
def process_9(num,roman_str):
    for (k,v) in romans:
        if (v > num):
            current_roman = romans[romans.index((k,v))]
            prev_roman = romans[romans.index((k,v)) - 2]
            roman_str += (prev_roman[0] + current_roman[0])
            num -= (current_roman[1] - prev_roman[1])
            break
    return num,roman_str
def process_4(num,roman_str):
    for (k,v) in romans:
        if (v > num):
            current_roman = romans[romans.index((k,v))]
            prev_roman = romans[romans.index((k,v)) - 1]
            roman_str += (prev_roman[0] + current_roman[0])
            num -= (current_roman[1] - prev_roman[1])
            break
    return num,roman_str
def process_other(num,roman_str):
    for (k,v) in romans_rev:
        div = num // v
        if ( div != 0 and num > 0 ):
            roman_str += k * div
            num -= v * div
            break
    return num,roman_str
def get_roman(num):
    final_roman_str = ""
    while (num > 0):
        if (str(num).startswith('4')):
            num,final_roman_str = process_4(num,final_roman_str)
        elif(str(num).startswith('9')):
            num,final_roman_str = process_9(num,final_roman_str)
        else:
            num,final_roman_str = process_other(num,final_roman_str)
    return final_roman_str

return get_roman(data)


print(checkio(number))
0
def test(num):

    try:
        if type(num) != type(1):
            raise Exception("expected integer, got %s" % type(num))
        if not 0 < num < 4000:
            raise Exception("Argument must be between 1 and 3999")
        ints = (1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1)
        nums = ('M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I')
        result = ""
        for i in range(len(ints)):
            count = int(num / ints[i])
            result += nums[i] * count
            num -= ints[i] * count
        print result
    except Exception as e:
        print e.message
0
roman_map = [(1000, 'M'), (900, 'CM'), (500, 'D'), (400, 'CD'), (100, 'C'), (90, 'XC'),
(50, 'L'), (40, 'XL'), (10, 'X'), (9, 'IX'), (5, 'V'), (4, 'IV'), (1, 'I')]

def IntToRoman (xn):
    x = xn
    y = 0
    Str = ""
    for i, r in roman_map:
         # take the number and divisible by the roman number from 1000 to 1.
        y = x//i    

        for j in range(0, y):
            # If after divisibility is not 0 then take the roman number from list into String.
            Str = Str+r 

        # Take the remainder to next round.
        x = x%i 
    print(Str)
    return Str

Test case:

>>> IntToRoman(3251)
MMMCCLI
'MMMCCLI'
0

start subtracting 1000,900... to 1 from A and stops when it finds positive.add corresponding roman to ans and make A to A-i where i is (1,4,5,9,10.....) repeat while A does not become 0.

def intToRoman(self, A):
    l=[[1,'I'],[4,'IV'],[5,'V'],[9,'IX'],[10,'X'],[40,'XL'],[50,'L'],
       [90,'XC'],[100,'C'],[400,'CD'],[500,'D'],[900,'CM'],[1000,'M']]
    ans=""
    while(A>0):
        for i,j in l[::-1]:
            if A-i>=0:
                ans+=j
                A=A-i
                break
    return ans
1
  • Please add some description about what your code does.
    – CLAbeel
    Commented Jun 21, 2018 at 20:01
0

This is my recursive function approach to convert a number to its roman equivalent

    def solution(n):
        # TODO convert int to roman string
        string=''
        symbol=['M','D','C','L','X','V','I']
        value = [1000,500,100,50,10,5,1]
        num = 10**(len(str(n))-1)
        quo = n//num
        rem=n%num
        if quo in [0,1,2,3]:
            string=string+symbol[value.index(num)]*quo
        elif quo in [4,5,6,7,8]:
            tem_str=symbol[value.index(num)]+symbol[value.index(num)-1]
                    +symbol[value.index(num)]*3
            string=string+tem_str[(min(quo,5)-4):(max(quo,5)-3)]
        else:
            string=string+symbol[value.index(num)]+symbol[value.index(num)-2]
        if rem==0:
            return string
        else:
            string=string+solution(rem)
        return string

print(solution(499))
print(solution(999))
print(solution(2456))
print(solution(2791))
CDXCIX
CMXCIX
MMCDLVI
MMDCCXCI
0

Another way. I wrote recursive loop by roman symbols, so the max depth of recursion equals length of roman tuple:

ROMAN = ((1000, 'M'), (900, 'CM'), (500, 'D'), (400, 'CD'),
    (100, 'C'), (90, 'XC'), (50, 'L'), (40, 'XL'),
    (10, 'X'), (9, 'IX'), (5, 'V'), (4, 'IV'), (1, 'I'))

def get_romans(number):
    return do_recursive(number, 0, '')


def do_recursive(number, index, roman):
    while number >= ROMAN[index][0]:
        number -= ROMAN[index][0]
        roman += ROMAN[index][1]
    if number == 0:
        return roman
    return check_recursive(number, index + 1, roman)


if __name__ == '__main__':
    print(get_romans(7))
    print(get_romans(78))
0

Here is my approach to solving this. The given number is first converted to a string so that then we can easily iterate over every digit to get the Roman part for the corresponding digit. To get the roman part for each digit I have grouped the Roman letters into 1 and 5 for each decimal position so the list of roman characters grouped based on decimal positions would look like [['I', 'V'], ['X', 'L'], ['C', 'D'], ['M']], where characters follow the order or ones, tens, hundreds, and thousands.

So we have digits to loop over and the roman characters for each order of the decimal place, we just need to prepare digits 0-9 using the above character list. The variable "order" picks the correct set of characters based on the decimal position of current digit, this is handled automatically as we are going from highest decimal place to lowest. Following is the full code:

def getRomanNumeral(num):
    # ---------- inner function -------------------
    def makeRomanDigit(digit, order):    
        chars = [['I', 'V'], ['X', 'L'], ['C', 'D'], ['M']]
        if(digit == 1):
            return chars[order][0]
        if(digit == 2):
            return chars[order][0] + chars[order][0]
        if(digit == 3):
            return chars[order][0] + chars[order][0] + chars[order][0]
        if(digit == 4):
            return  chars[order][0] + chars[order][1]
        if(digit == 5):
            return chars[order][1]
        if(digit == 6):
            return chars[order][1] + chars[order][0]
        if(digit == 7):
            return chars[order][1] + chars[order][0] + chars[order][0]
        if(digit == 8):
            return chars[order][1] + chars[order][0] + chars[order][0] + chars[order][0]
        if(digit == 9):
            return chars[order][0] + chars[order+1][0]
        if(digit == 0):
            return ''
    #--------------- main -----------------
    str_num = str(num)
    order = len(str_num) - 1
    result = ''
    for digit in str_num:
        result += makeRomanDigit(int(digit), order)
        order-=1
    return result

A few tests:

getRomanNumeral(112)
'CXII'

getRomanNumeral(345)
'CCCXLV'

getRomanNumeral(591)
'DXCI'

getRomanNumeral(1000)
'M'

I know a lot can be improved in the code or approach towards the problem, but this was my first attempt at this problem.

0

I have produced an answer that works for any int >= 0:

Save the following as romanize.py

def get_roman(input_number: int, overline_code: str = '\u0305') -> str:
    """
    Recursive function which returns roman numeral (string), given input number (int)

    >>> get_roman(0)
    'N'
    >>> get_roman(3999)
    'MMMCMXCIX'
    >>> get_roman(4000)
    'MV\u0305'
    >>> get_roman(4000, overline_code='^')
    'MV^'
    """
    if input_number < 0 or not isinstance(input_number, int):
        raise ValueError(f'Only integers, n, within range, n >= 0 are supported.')
    if input_number <= 1000:
        numeral, remainder = core_lookup(input_number=input_number)
    else:
        numeral, remainder = thousand_lookup(input_number=input_number, overline_code=overline_code)
    if remainder != 0:
        numeral += get_roman(input_number=remainder, overline_code=overline_code)
    return numeral


def core_lookup(input_number: int) -> (str, int):
    """
    Returns highest roman numeral (string) which can (or a multiple thereof) be looked up from number map and the
    remainder (int).

    >>> core_lookup(3)
    ('III', 0)
    >>> core_lookup(999)
    ('CM', 99)
    >>> core_lookup(1000)
    ('M', 0)
    """
    if input_number < 0 or input_number > 1000 or not isinstance(input_number, int):
        raise ValueError(f'Only integers, n, within range, 0 <= n <= 1000 are supported.')
    basic_lookup = NUMBER_MAP.get(input_number)
    if basic_lookup:
        numeral = basic_lookup
        remainder = 0
    else:
        multiple = get_multiple(input_number=input_number, multiples=NUMBER_MAP.keys())
        count = input_number // multiple
        remainder = input_number % multiple
        numeral = NUMBER_MAP[multiple] * count
    return numeral, remainder


def thousand_lookup(input_number: int, overline_code: str = '\u0305') -> (str, int):
    """
    Returns highest roman numeral possible, that is a multiple of or a thousand that of which can be looked up from
    number map and the remainder (int).

    >>> thousand_lookup(3000)
    ('MMM', 0)
    >>> thousand_lookup(300001, overline_code='^')
    ('C^C^C^', 1)
    >>> thousand_lookup(30000002, overline_code='^')
    ('X^^X^^X^^', 2)
    """
    if input_number <= 1000 or not isinstance(input_number, int):
        raise ValueError(f'Only integers, n, within range, n > 1000 are supported.')
    num, k, remainder = get_thousand_count(input_number=input_number)
    numeral = get_roman(input_number=num, overline_code=overline_code)
    numeral = add_overlines(base_numeral=numeral, num_overlines=k, overline_code=overline_code)

    # Assume:
    # 4000 -> MV^, https://en.wikipedia.org/wiki/4000_(number)
    # 6000 -> V^M, see https://en.wikipedia.org/wiki/6000_(number)
    # 9000 -> MX^, see https://en.wikipedia.org/wiki/9000_(number)
    numeral = numeral.replace(NUMBER_MAP[1] + overline_code, NUMBER_MAP[1000])
    return numeral, remainder


def get_thousand_count(input_number: int) -> (int, int, int):
    """
    Returns three integers defining the number, number of thousands and remainder

    >>> get_thousand_count(999)
    (999, 0, 0)
    >>> get_thousand_count(1001)
    (1, 1, 1)
    >>> get_thousand_count(2000002)
    (2, 2, 2)
    """
    num = input_number
    k = 0
    while num >= 1000:
        k += 1
        num //= 1000
    remainder = input_number - (num * 1000 ** k)
    return num, k, remainder


def get_multiple(input_number: int, multiples: iter) -> int:
    """
    Given an input number(int) and a list of numbers, finds the number in list closest (rounded down) to input number

    >>> get_multiple(45, [1, 2, 3])
    3
    >>> get_multiple(45, [1, 2, 3, 44, 45, 46])
    45
    >>> get_multiple(45, [1, 4, 5, 9, 10, 40, 50, 90])
    40
    """
    options = sorted(list(multiples) + [input_number])
    return options[options.index(input_number) - int(input_number not in multiples)]


def add_overlines(base_numeral: str, num_overlines: int = 1, overline_code: str = '\u0305') -> str:
    """
    Adds overlines to input base numeral (string) and returns the result.

    >>> add_overlines(base_numeral='II', num_overlines=1, overline_code='^')
    'I^I^'
    >>> add_overlines(base_numeral='I^I^', num_overlines=1, overline_code='^')
    'I^^I^^'
    >>> add_overlines(base_numeral='II', num_overlines=2, overline_code='^')
    'I^^I^^'
    """
    return ''.join([char + overline_code*num_overlines if char.isalnum() else char for char in base_numeral])


def gen_number_map() -> dict:
    """
    Returns base number mapping including combinations like 4 -> IV and 9 -> IX, etc.
    """
    mapping = {
        1000: 'M',
        500: 'D',
        100: 'C',
        50: 'L',
        10: 'X',
        5: 'V',
        1: 'I',
        0: 'N'
    }
    for exponent in range(3):
        for num in (4, 9,):
            power = 10 ** exponent
            mapping[num * power] = mapping[1 * power] + mapping[(num + 1) * power]
    return mapping


NUMBER_MAP = gen_number_map()


if __name__ == '__main__':
    import doctest
    doctest.testmod(verbose=True, raise_on_error=True)
    # Optional extra tests
    # doctest.testfile('test_romanize.txt', verbose=True)

Here are some extra tests in case useful. Save the following as test_romanize.txt in the same directory as the romanize.py:

The ``romanize`` module
=======================


The ``get_roman`` function
--------------------------

Import statement:

    >>> from romanize import get_roman

Tests:

    >>> get_roman(0)
    'N'
    >>> get_roman(6)
    'VI'
    >>> get_roman(11)
    'XI'
    >>> get_roman(345)
    'CCCXLV'
    >>> get_roman(989)
    'CMLXXXIX'
    >>> get_roman(989000000, overline_code='^')
    'C^^M^^L^^X^^X^^X^^M^X^^'
    >>> get_roman(1000)
    'M'
    >>> get_roman(1001)
    'MI'
    >>> get_roman(2000)
    'MM'
    >>> get_roman(2001)
    'MMI'
    >>> get_roman(900)
    'CM'
    >>> get_roman(4000, overline_code='^')
    'MV^'
    >>> get_roman(6000, overline_code='^')
    'V^M'
    >>> get_roman(9000, overline_code='^')
    'MX^'
    >>> get_roman(6001, overline_code='^')
    'V^MI'
    >>> get_roman(9013, overline_code='^')
    'MX^XIII'
    >>> get_roman(70000000000, overline_code='^')
    'L^^^X^^^X^^^'
    >>> get_roman(9000013, overline_code='^')
    'M^X^^XIII'
    >>> get_roman(989888003, overline_code='^')
    'C^^M^^L^^X^^X^^X^^M^X^^D^C^C^C^L^X^X^X^V^MMMIII'


The ``get_thousand_count`` function
--------------------------

Import statement:

    >>> from romanize import get_thousand_count

Tests:

    >>> get_thousand_count(13)
    (13, 0, 0)
    >>> get_thousand_count(6013)
    (6, 1, 13)
    >>> get_thousand_count(60013)
    (60, 1, 13)
    >>> get_thousand_count(600013)
    (600, 1, 13)
    >>> get_thousand_count(6000013)
    (6, 2, 13)
    >>> get_thousand_count(999000000000000000000000000999)
    (999, 9, 999)
    >>> get_thousand_count(2005)
    (2, 1, 5)
    >>> get_thousand_count(2147483647)
    (2, 3, 147483647)


The ``core_lookup`` function
--------------------------

Import statement:

    >>> from romanize import core_lookup

Tests:

    >>> core_lookup(2)
    ('II', 0)
    >>> core_lookup(6)
    ('V', 1)
    >>> core_lookup(7)
    ('V', 2)
    >>> core_lookup(19)
    ('X', 9)
    >>> core_lookup(900)
    ('CM', 0)
    >>> core_lookup(999)
    ('CM', 99)
    >>> core_lookup(1000)
    ('M', 0)
    >>> core_lookup(1000.2)
    Traceback (most recent call last):
    ValueError: Only integers, n, within range, 0 <= n <= 1000 are supported.
    >>> core_lookup(10001)
    Traceback (most recent call last):
    ValueError: Only integers, n, within range, 0 <= n <= 1000 are supported.
    >>> core_lookup(-1)
    Traceback (most recent call last):
    ValueError: Only integers, n, within range, 0 <= n <= 1000 are supported.


The ``gen_number_map`` function
--------------------------

Import statement:

    >>> from romanize import gen_number_map

Tests:

    >>> gen_number_map()
    {1000: 'M', 500: 'D', 100: 'C', 50: 'L', 10: 'X', 5: 'V', 1: 'I', 0: 'N', 4: 'IV', 9: 'IX', 40: 'XL', 90: 'XC', 400: 'CD', 900: 'CM'}


The ``get_multiple`` function
--------------------------

Import statement:

    >>> from romanize import get_multiple
    >>> multiples = [0, 1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000]

Tests:

    >>> get_multiple(0, multiples)
    0
    >>> get_multiple(1, multiples)
    1
    >>> get_multiple(2, multiples)
    1
    >>> get_multiple(3, multiples)
    1
    >>> get_multiple(4, multiples)
    4
    >>> get_multiple(5, multiples)
    5
    >>> get_multiple(6, multiples)
    5
    >>> get_multiple(9, multiples)
    9
    >>> get_multiple(13, multiples)
    10
    >>> get_multiple(401, multiples)
    400
    >>> get_multiple(399, multiples)
    100
    >>> get_multiple(100, multiples)
    100
    >>> get_multiple(99, multiples)
    90


The ``add_overlines`` function
--------------------------

Import statement:

    >>> from romanize import add_overlines

Tests:

    >>> add_overlines('AB')
    'A\u0305B\u0305'
    >>> add_overlines('A\u0305B\u0305')
    'A\u0305\u0305B\u0305\u0305'

    >>> add_overlines('AB', num_overlines=3, overline_code='^')
    'A^^^B^^^'
    >>> add_overlines('A^B^', num_overlines=1, overline_code='^')
    'A^^B^^'

    >>> add_overlines('AB', num_overlines=3, overline_code='\u0305')
    'A\u0305\u0305\u0305B\u0305\u0305\u0305'
    >>> add_overlines('A\u0305B\u0305', num_overlines=1, overline_code='\u0305')
    'A\u0305\u0305B\u0305\u0305'

    >>> add_overlines('A^B', num_overlines=3, overline_code='^')
    'A^^^^B^^^'

    >>> add_overlines('A^B', num_overlines=0, overline_code='^')
    'A^B'
0

The code for this roman numeral does not check for errors like wrong letters it is just for a perfect roman numeral letters

roman_dict = {'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 = input('Enter the roman numeral: ').upper()
roman_initial = roman # used to retain the original roman figure entered

lst = []


while roman != '':    
if len(roman) > 1:
    check = roman[0] + roman[1]
    if check in roman_dict and len(roman) > 1:
        lst.append(check)
        roman = roman[roman.index(check[1])+1:]
    else:
        if check not in roman_dict and len(roman) > 1:
            lst.append(check[0])
            roman = roman[roman.index(check[0])+1:]   
else:
    if len(roman)==1:
        check = roman[0]
        lst.append(check[0])
        roman = ''

if lst != []:
Sum = 0
for i in lst:
    if i in roman_dict:
        Sum += roman_dict[i]
print('The roman numeral %s entered is'%(roman_initial),Sum)
0

Here is a simpler way, i followed the basic conversion method explained here

Please find the code below:

input = int(raw_input()) # enter your integerval from keyboard
Decimals =[1,4,5,9,10,40,50,90,100,400,500,900,1000]    
Romans =['I','IV','V','IX','X','XL','L','XC','C','CD','D','CM','M']
romanletters= [] # empty array to fill each corresponding roman letter from the Romans array
while input != 0: 
    # we use basic formula of converstion, therefore input is substracted from 
    # least maximum similar number from Decimals array until input reaches zero 
    for val in range (len(Decimals)):
        if input >= Decimals[val]:
            decimal = Decimals[val]
            roman = Romans[val]
    difference = input - decimal
    romanletters.append(roman)
    input = difference
dec_to_roman = ''.join(romanletters) # concatinate the values
print dec_to_roman
0

I think this is the simplest approach. Roman Numerals have a range 1 to 3999.

def toRomanNumeral(n):
  NumeralMatrix = [
      ["", "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"],
      ["", "M", "MM", "MMM"]
  ]
  # The above matrix helps us to write individual digits of the input as roman numeral.
  # Rows corresponds to the place of the digit (ones, tens, hundreds, etc).
  # Column corresponds to the digit itself.
  
  rn = ""
  d = []
  # d is an array to store the individual digits of the input 

  while (n!=0):
      d.append(n%10)
      n =int(n/10)
  
  for i in range(len(d), 0, -1):
      rn += NumeralMatrix[i-1][d[i-1]]
      # [i-1] is the digit's place (0 - ones, 1 - tens, etc)
      # [d[i-1]] is the digit itself

  return rn

print(toRomanNumeral(49))
# XLIX
0

I grabbed this from my GitHub (you can find a more detailed version there). This function has a fixed computation time and makes use of no external libraries. It works for all integers between 0 and 1000.

def to_roman(n):
    try:
    if n >= 0 and n <= 1000:
        d = [{'0':'','1':'M'},
             {'0':'','1':'C','2':'CC','3':'CCC','4':'DC','5':'D',
              '6':'DC','7':'DCC','8':'DCCC','9':'MC'},
             {'0':'','1':'X','2':'XX','3':'XXX','4':'XL','5':'L',
              '6':'LX','7':'LXX','8':'LXXX','9':'CX'},
             {'0':'','1':'I','2':'II','3':'III','4':'IV','5':'V',
              '6':'VI','7':'VII','8':'VIII','9':'IX'}]
        x = str('0000' + str(n))[-4:]
        r = ''
        for i in range(4):
        r = r + d[i][x[i]]
        return r
    else:
        return '`n` is out of bounds.'
    except:
        print('Is this real life?\nIs `n` even an integer?')

Output from entering n=265:

>>> to_roman(265)
    'CCLXV'
0
def convert_to_roman(number, rom_denom, rom_val):
    '''Recursive solution'''
    #Base case
    if number == 0:
        return ''
    else:
        rom_str = (rom_denom[0] * (number//rom_val[0])) + convert_to_roman(number % 
        rom_val[0], rom_denom[1:], rom_val[1:]) #Recursive call
    return rom_str

rom_denom = ['M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I']
rom_val = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
number = int(input("Enter numeral to convert: "))
rom_str = convert_to_roman(number, rom_denom, rom_val)
print(rom_str)
0
def convert_to_roman(num):
    # print(num)
    roman=""
    while(num!=0):
        if num in range(1000,5000):
            roman+="M"*(num//1000)
            num-=(num//1000)*1000

        if num in range(900,1000):          #range(a,b) works from a to b-1 here from 900->999
            roman+="CM"
            num-=900

        if num in range(500,900):
            roman+="D"
            num-=500

        if num in range(400,500):
            roman+="CD"
            num-=400

        if num in range(100,400):
            roman+="C"*(num//100)
            num-=100*(num//100)

        if num in range(90,100):
            roman+="XC"
            num-=90

        if num in range(50,90):
            roman+="L"
            num-=50

        if num in range(40,50):
            roman+="XL"
            num-=40

        if num in range(10,40):
            roman+="X"*(num//10)
            num-=10*(num//10)

        if num==9:
            roman+="IX"
            num-=9

        if num in range(5,9):
            roman+="V"
            num-=5

        if num ==4:
            roman+="IV"
            num-=4

        if num ==3:
            roman+="III"
            num-=3

        if num ==2:
            roman+="II"
            num-=2

        if num ==1:
            roman+="I"
            num-=1


    # print(num)    
    return roman
    #Start writing your code here
num=888
print(num,":",convert_to_roman(num))
1
  • Hi. Welcome to StackOverflow and thank you for jumping in and contributing! You have chosen to answer an older question, which has an accepted answer and many alternative answers. Could you perhaps explain the advantages of your approach, as you see it? Thank you, and I hope you enjoy, and get a lot out of, StackOverflow.
    – Basya
    Commented Jun 1, 2021 at 18:07
0

A simple string concatenation through iteration:

def roman(x: int) -> str:
    """Convert into to roman numeral"""
    # Iterative
    L = [(1000, 'M'), (900, 'CM'), (500, 'D'), (400, 'CD'),
         (100, 'C'), (90, 'XC'), (50, 'L'), (40, 'XL'),
         (10, 'X'), (9, 'IX'), (5, 'V'), (4, 'IV'), (1, 'I')]

    if x >= 1:
        y = ""
        for val, sym in L:
            y += sym*(x//val)
            x -= val*(x//val)
    else:
        return None
    return y
>>> roman(399)
'CCCXCIX'
0
class Solution:
    def intToRoman(self, num: int) -> str:
        if num>3999:

            return
        value = [1000,900,500,400,100,90,50,40,10,9,5,4,1]
        sym = ["M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"]
        index = 0
        result = ""
        while num>0:
            res = num//value[index]
            num = num%value[index]
            while res>0:
                result = result+sym[index]
                res =res-1

            index+=1
        return  result
if __name__ == '__main__':
    obj = Solution()
    print(obj.intToRoman(1994))
0

Not the answer you're looking for? Browse other questions tagged or ask your own question.