19

This is now my current code after what user2486 said.

def romanMap():
    map=(("M",  1000),("CM", 900),("D",  500),("CD", 400),("C",  100),("XC", 90),("L",  50),("XL", 40),("X",  10),("IX", 9),("V",  5),("V", 4),("I",  1))
    return map
firstNum=ns([0])
secondNum=ns([1])
def main():
    ns=str(input("Enter a roman numeral"))
    total=0
    result=0
    while ns:
        firstNum=(romanMap(ns[0]))
         secondNum=(romanMap(ns[1]) 
        if firstNum is len(ns)>1 or secondNum-1:
                        total=total+firstNum
            ns=ns[1:]
        else:
                        total=total+ns[1]-ns[0]
            ns=ns[2:]
      print (total)
main()

I am getting this error with while ns: UnboundLocalError: local variable 'ns' referenced before assignment

Jake
  • 357
  • 2
  • 4
  • 14
  • 3
    Thats the tedious and harder way to do it, it would be more fficient if you used a dictionary. For reference take a look at an already written functionality to do that, http://svn.python.org/projects/python/branches/pep-0384/Doc/tools/roman.py –  Oct 11 '13 at 00:02
  • 9
    I... can't imagine those functions are doing what you want them to do. – roippi Oct 11 '13 at 00:03
  • 2
    @roippi -- of course not. If they were, OP might not be here asking a question :) – mgilson Oct 11 '13 at 00:05
  • @mgilson well yeah, but he just goes on to say that his program doesn't work. If I have a bunch of bricks made out of mashed potatoes and try to build a house out of them, I don't say "my house doesn't work." – roippi Oct 11 '13 at 00:08
  • 1
    @roippi no, you start a kickstarter about making houses out of recycled potatoes and use the money to solve your problem. – kojiro Oct 11 '13 at 00:12
  • Please don't change a question so drastically - create a new question instead! Anyway, an "UnboundLocalError" means that a variable has not been assigned to and it has been attempted to be used! In this case, it's missing the assignment to `ns` from the user input. Also note 1) that `ns[1]` might throw an exception if `len(ns) == 1`; 2) the initial assignments outside the loop to `firstNum/secondNum` do nothing; 3) the total should only be computed *from* `firstNum/secondNum` as `ns[x]` returns a string; 4) implement a function called `numberOfNumeral` that does what I suggested, and use it ;) – user2864740 Oct 11 '13 at 22:11
  • To add to @enginefree, the roman module birth is described in Dive Into Python 3 -- see http://stackoverflow.com/a/19313140/1346705 for links. – pepr May 03 '17 at 10:15

17 Answers17

33

No need to reinvent the wheel (unless you want to). Python once came with a converter (so you can go to the Python 3.4.1 source code and grab the module at this location: /Python-3.4.1/Doc/tools/roman.py; or perhaps install it with pip as someone in the comments said here; I haven't verified the pip version; anyway, then you can do):

import roman;
n=roman.fromRoman("X"); #n becomes 10

If you need it for numbers 5000 and above, you'll need to write a new function, though, and maybe make your own font to represent the lines over the roman numerals. (It will only work with some numbers, at that. Stopping at 4999 is a really good idea.)

To convert to roman numerals, use roman.toRoman(myInt).

Alternatively (for converting to Roman numerals only), you can do this in Python 3.9.2 (which I only partially understand due to a lack of documentation; so, all my arguments probably aren't right; but, it seems to work; formatter is depreciated anyway; so, don't expect it to stay around a long time):

import formatter
a=formatter.AbstractFormatter("I don't know what I'm supposed to put here, but it doesn't seem to matter for our purposes.")
roman_numeral=a.format_roman(case="I", counter=5) #Case doesn't seem to matter, either.
#roman_numeral now equals "V"

Someone else actually linked to the same source code the roman module uses in one of the comments above, but I don't believe they mentioned that it actually comes with Python. It doesn't seem to come with Python anymore, but it did in version 3.4.1.

Brōtsyorfuzthrāx
  • 4,387
  • 4
  • 34
  • 56
11

Roman numerals are read from left to right, as you add or subtract the value of each symbol.

If a value is lower than the following value, it will be subtracted. Otherwise it will be added.

For example, we want to conver the Roman numeral MCMLIV to an Arabic number:

M = 1000 must be added, because the following letter C =100 is lower.
C = 100 must be subtracted because the following letter M =1000 is greater.
M = 1000 must be added, because the following letter L = 50 is lower.
L = 50 must be added, because the following letter I =1 is lower.
I = 1 must be subtracted, because the following letter V = 5 is greater.
V = 5 must be added, because there are no more symbols left.

We can now calculate the number:

1000 - 100 + 1000 + 50 - 1 + 5 = 1954 

ref : http://www.mathinary.com/roman_numerals_from_roman_numerals_to_arabic_numbers.jsp

def from_roman(num):
    roman_numerals = {'I':1, 'V':5, 'X':10, 'L':50, 'C':100, 'D':500, 'M':1000}
    result = 0
    for i,c in enumerate(num):
        if (i+1) == len(num) or roman_numerals[c] >= roman_numerals[num[i+1]]:
            result += roman_numerals[c]
        else:
            result -= roman_numerals[c]
    return result
r366y
  • 382
  • 6
  • 17
7

Consider this additional pseudo-code and hints (some of it is valid Python, some isn't, but there be notes).

def numberOfNumeral(n):
    """ Return the number represented by the single numeral """
    # e.g. "v" -> 5, "i" -> 5 (and handle v/V cases, etc.)

# avoid "string" as a variable name
# I chose "ns" for "numerals" (which might be better),
# but I'm also a bit terse .. anyway, name variables for what they represents.
ns = str(input("Enter a roman numeral"))

while ns:
   firstNum = numberOfNumeral(ns[0])
   # This makes secondValue = -1 when there is only one numeral left
   # so firstNum is always "at least" secondNum when len(ns) == 1. 
   secondNum = numberOfNumeral(ns[1]) if len(ns) > 1 else -1
   if firstNum is at least secondNum:
      # Add firstNum to total.
      # Remove the character - so that the loop state advances.
      # If we don't don't his, as in the original, it will never end.
      # Here we use "slice notation".
      ns = ns[1:] 
   else:
      # Add the difference, secondNum - firstNum, to total.
      # Remove both characters - again, so we advance state.
      ns = ns[2:]
Community
  • 1
  • 1
user2864740
  • 60,010
  • 15
  • 145
  • 220
5

A nice compact version with no external libraries:

def rn_to_int(s):
  d = {'m': 1000, 'd': 500, 'c': 100, 'l': 50, 'x': 10, 'v': 5, 'i': 1}
  n = [d[i] for i in s.lower() if i in d]
  return sum([i if i>=n[min(j+1, len(n)-1)] else -i for j,i in enumerate(n)])


for numeral, expected in [['CLXIV', 164], ['MDCCLXXXIII', 1783], ['xiv', 14]]:
  assert rn_to_int(numeral) == expected
duhaime
  • 25,611
  • 17
  • 169
  • 224
  • Any idea how to fix that? `Index 1 0 2 0 3 0 4 0 5 0 .. 65 II 66 II 67 II 68 II 69 II Name: 0, Length: 69, dtype: object Traceback (most recent call last): File "C:\pandas\data.py", line 115, in rn_to_int(xAs) File "C:\pandas\data.py", line 111, in rn_to_int n = [d[i] for i in s.lower() if i in d] File "C:\Python39\lib\site-packages\pandas\core\generic.py", line 5902, in __getattr__ return object.__getattribute__(self, name) AttributeError: 'Series' object has no attribute 'lower'` – Lod Aug 15 '23 at 11:07
  • 1
    looks like you're tryint to use the `rn_to_int` function on a pandas series. You should run it on a string – duhaime Aug 17 '23 at 15:50
  • thanks for the suggestion. I have a dataframe with nans converted to 0s and getting this one: https://onecompiler.com/python/3zhu254qe Any suggestions to modify to ignore zeros? thanks so much. – Lod Aug 17 '23 at 18:44
  • For now this seems a working fix: https://onecompiler.com/python/3zhtzvyeu from this ref: https://stackoverflow.com/a/69279029/10789707 – Lod Aug 17 '23 at 18:54
  • 1
    Amazing! It worked very well as expected! Thank again. Be well! – Lod Aug 17 '23 at 19:01
4

Right-to-left solution that is a bit more Pythonic (no indexes) and relatively short.

Algorithm:

  • Reverse the roman numeral and map it to a list of numbers
  • Figure out which numbers should be subtracted and then sum the list

Example:

'xiv' => sum(5, -1, 10) => 14

def parse_roman(s):
    numerals = {'M':1000, 'D':500, 'C':100, 'L':50, 'X':10, 'V':5, 'I':1}
    n = 0
    last_value = 0
    # e.g. convert 'xiv' to (5, 1, 10)
    for value in (numerals[c] for c in reversed(s.upper())):
        # debugging
        v = (value, -value)[value < last_value]
        print('{:6} += {:5}  <== cur, prev = {}, {}'.format(n, v, value, last_value))
        # subtract smaller values that come after larger ones, otherwise add
        n += (value, -value)[value < last_value]
        last_value = value
    return n

Output:

parse_roman('MMCMXCVIII')
     0 +=     1  <== cur, prev = 1, 0
     1 +=     1  <== cur, prev = 1, 1
     2 +=     1  <== cur, prev = 1, 1
     3 +=     5  <== cur, prev = 5, 1
     8 +=   100  <== cur, prev = 100, 5
   108 +=   -10  <== cur, prev = 10, 100
    98 +=  1000  <== cur, prev = 1000, 10
  1098 +=  -100  <== cur, prev = 100, 1000
   998 +=  1000  <== cur, prev = 1000, 100
  1998 +=  1000  <== cur, prev = 1000, 1000
2998

Note: It would be nice to find a (short, inline) method for changing the signs of the sequence on the fly. For example, (5, 1, 10) ==> (5, -1, 10).


Update: This is as close as I got before giving up. It's identical to the code above, but it uses itertools.tee() with zip() to generate pairs of the previous and current values to eliminate the need for the state variables. The single call to next(cur) makes that list one shorter than prev which is all the state we need to figure out whether to add or subtract the current value.

Example:

cur, prev = (5, 1, 10), (5, 1, 10)
# Take one from cur and zip the rest
next(cur) + sum(... zip(cur, prev))
# 5 + ... zip( (1, 10), (5, 1, 10) )  ==>  5 + ... ((1, 5), (10, 1)) 

Code:

from itertools import tee

def parse_roman(s):
    numerals = {'M':1000, 'D':500, 'C':100, 'L':50, 'X':10, 'V':5, 'I':1}
    cur, prev = tee(numerals[c] for c in reversed(s.upper()))
    return next(cur) + sum((cur, -cur)[cur < prev] for cur, prev in zip(cur,prev))
Harvey
  • 5,703
  • 1
  • 32
  • 41
3

Okay, there are a lot of things wrong with what you currently have.

First, the reason you are getting a bunch of 0's is because you are never exiting your while string != "": loop, and it isn't ever adding integers to the total. So total remains zero, and keeps getting printed. I've commented the code you posted to help you understand what is going on.

def main():
    string=str(input("Enter a roman numeral"))
    total=0
    while string != "": # Empty strings evaluate as False, this can just be 'while string:'
        if string[1] == string[2] or string == len([1]): # Here you are testing the 2nd and 3rd elements.
                                                         # Also, you want to do len(string) == 1
                                                         # string will never == len([1]), so you never
                                                         # execute the code in this block.
            total += string[1]+1   # You want to add the corresponding value of string[0], use a dictionary.
        print (total)

        # Missing the else statement in the pseudocode.
main()

user2864740 has some good comments in their posted solution, look over that to see some of the things you were doing wrong.

Here is Python (2.7 unfortunately) code that does what your given pseudocode says.

val = {'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000}

def main():
    string = str(raw_input('Enter a roman numeral: '))
    string = string.upper()
    total = 0
    while string:
        if len(string) == 1 or val[string[0]] >= val[string[1]]:
            total += val[string[0]]
            string = string[1:]
        else:
            total += val[string[1]] - val[string[0]]
            string = string[2:]
    print total

main()

Please note that the pseudocode you posted is NOT correct. Note what it will do for the input 'IIV'. It will subtract 1 from 1, then add 5. But what it should do is subtract 2 from 5.

Peacemaker636
  • 97
  • 1
  • 1
  • 8
3

Here is my solution:

numerals = [
        {'letter': 'M', 'value': 1000},
        {'letter': 'D', 'value': 500},
        {'letter': 'C', 'value': 100},
        {'letter': 'L', 'value': 50},
        {'letter': 'X', 'value': 10},
        {'letter': 'V', 'value': 5},
        {'letter': 'I', 'value': 1},
    ]

def arabic_to_roman(number):
    remainder = number
    result = ''
    for numeral_index in xrange(len(numerals)):
        numeral = numerals[numeral_index]
        next_numeral = numerals[numeral_index + 1] if numeral_index + 1 < len(numerals) else None

        factor = remainder / numeral['value']
        remainder -= factor * numeral['value']

        if next_numeral:
            numeral_difference = numeral['value'] - next_numeral['value']
            if (remainder - numeral_difference >= 0) and (numeral_difference > next_numeral['value']):
                result += next_numeral['letter'] + numeral['letter']
                remainder -= numeral_difference

        if factor > 0:
            result += numeral['letter'] * factor

    return result


def roman_to_arabic(number):
    index_by_letter = {}
    for index in xrange(len(numerals)):
        index_by_letter[numerals[index]['letter']] = index

    result = 0
    previous_value = None
    for letter in reversed(number):
        index = index_by_letter[letter]
        value = numerals[index]['value']
        if (previous_value is None) or (previous_value <= value):
            result += value
        else:
            result -= value
        previous_value = value

    return result
Valentin Shergin
  • 7,166
  • 2
  • 50
  • 53
2

I know this is an old post, but I would like to add 3 solutions to convert roman numerals to numbers.

Solution 1: (Approx Runtime = 52ms)

def romanToInt(self, s: str) -> int:

     roman = {'I':1, 'V':5, 'X':10, 'L':50, 'C':100, 'D':500, 'M':1000 }    
     num = 0

     for i in range(len(s)):

        if i!= len(s)-1 and roman[s[i]] < roman[s[i+1]]:
             num += roman[s[i]]*-1
        else:
             num += roman[s[i]]

      return num

Solution 2: (Approx Runtime = 60ms)

def romanToInt(self, s: str) -> int:

     roman = {'I':1, 'V':5, 'X':10, 'L':50, 'C':100, 'D':500, 'M':1000 }    
     num = 0

     s = s.replace("IV", "IIII").replace("IX", "VIIII")
     s = s.replace("XL", "XXXX").replace("XC", "LXXXX")
     s = s.replace("CD", "CCCC").replace("CM", "DCCCC")

     for x in s:
        num += roman[x]

     return num

Solution 3: (Approx Runtime = 48ms)

def romanToInt(self, s: str) -> int:

     roman = {'I':1, 'V':5, 'X':10, 'L':50, 'C':100, 'D':500, 'M':1000 }    
     num = 0

     for i in range(len(s)-1):
        if roman[s[i]] < roman[s[i+1]]:
            num += roman[s[i]]*-1
            continue

         num += roman[s[i]]

      num +=roman[s[-1]]

      return num

The simplest solution appears to be the best at times :)

R.K
  • 1,721
  • 17
  • 22
1

what about This piece of Code

mapping = {'I': 1, 'V': 5, 'X': 10,'L': 50, 'C': 100, 'D': 500, 'M':1000}

def roman_to_dec(roman):
"""
Convert the roman no to decimal
"""
dec = last = 0
for i in range(0, len(roman)):
    no = mapping.get(roman[i])
    # subtract last 2 times cuz one for this pass and another for last pass
    dec = dec + (no - 2 * last) if no > last else dec + no
    last = no
return dec
runitfirst
  • 323
  • 1
  • 13
0

There is a very detailed description of the development of the Roman numeral converters in the Dive Into Python 3 by Mark Pilgrim. See the 5.3. Case Study: Roman Numerals that introduces the problem and details.

But that is not all. See the Chapter 9. Unit Testing where the analysis and the implementation of Roman numeral converters continues, including interesting optimization and exception throwing -- the (unit) test driven development.

It is directly related to the enginefree's reference to the code in the first comment below the question (the code was written by Mark Pilgrim).

pepr
  • 20,112
  • 15
  • 76
  • 139
0

Here's something i came up with using dictionary. It should be v.simple. Tell me what you think. I must say it does not handle the spoof roman numerals written in the form of MIM (instead of MCMXCIX for 1999). This is only for valid roman numerals.

import re
s = 0;
a = dict();
b = dict();
r = "MMCMXCVIII"

a['CM'] = 900;
a['IX'] = 9;
a ['IV'] = 4;
a ['XL'] = 40;
a ['CD'] = 400;
a ['XC'] = 90;

b['M'] = 1000;
b['C'] = 100;
b['D'] = 500;
b['X'] = 10;
b['V'] = 5;
b['L'] = 50;
b['I'] = 1;

# Handle the tricky 4's and 9's first and remove them from the string

for key in a:
        if key in r: 
            r = re.sub(key,'',r)
            s+=a[key];
# Then straightforward multiplication of the not-so-tricky ones by their count.

for key in b:
         s+= r.count(key) * b[key];

print s; # This will print 2998
Vicks
  • 1
0

You can use this code:

def roman_integer(roman):
    roman = roman.upper() # for taking care of upper or lower case letters
    integer_rep = 0
    roman_to_integer_map = tuple()
    roman_to_integer_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,4}              # thousands - 0 to 4 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)

    if not roman_numeral_pattern.search(roman):
        return 0
    index = 0
    for numeral, integer in roman_to_integer_map:
        while roman[index:index+len(numeral)] == numeral:
            #print numeral, integer, 'matched'
            integer_rep += integer
            index += len(numeral)
    return integer_rep
0

Work from right to left of the roman numeral to add or subtract values. Easy.

def rome(roman_num):
     d = {'I':1,'V':5,'X':10,'L':50,'C':100,'D':500,'M':1000}
     nl = list(roman_num)
     sum = d[nl[len(nl)-1]]
     for i in range(len(nl)-1,0,-1):
             if d[nl[i]]>d[nl[i-1]]:
                     sum -= d[nl[i-1]]
             else:
                     sum += d[nl[i-1]]       
     return sum
Gatis Seja
  • 87
  • 2
  • 2
  • 7
0
roman_conver=[  (1,'I'),
                (5,'V'),
                (10,'X'),
                (50,'L'),
                (100,'C'),
                (500,'D'),
                (1000,'M'),
                    ]
def romantonumeral(roman):
    tot = 0
    for i in range(0,len(roman)):
        for each in roman_conver:
            if roman[i]==each[1]:
                if each[0]>tot:
                    tot = each[0] - tot
                else:
                    tot = tot + each[0]
    return tot
Paolo
  • 20,112
  • 21
  • 72
  • 113
Mohan Meruva
  • 322
  • 1
  • 3
  • 11
0

This is one of the leetcode questions.

def romanToInt(s):
    sum=0
    dict={'M':1000,'D':500,'C':100,'L':50,'X':10,'V':5,'I':1}

    for i in range(len(s)):
        if i==0:
            sum=sum+dict[s[i]]
        else:
            if s[i]=='M':
                sum=sum+1000
                if s[i-1]=='C':
                    sum=sum-200      

            elif s[i]=='D':
                sum=sum+500
                if s[i-1]=='C':
                    sum=sum-200

            elif s[i]=='C':
                sum=sum+100
                if s[i-1]=='X':
                    sum=sum-20

            elif s[i]=='L':
                sum=sum+50
                if s[i-1]=='X':
                    sum=sum-20

            elif s[i]=='X':
                sum=sum+10
                if s[i-1]=='I':
                    sum=sum-2

            elif s[i]=='V':
                sum=sum+5
                if s[i-1]=='I':
                    sum=sum-2
            elif s[i]=='I':
                sum=sum+1
    return (sum)
0
def romanToInt(self, s: str) -> int:
    roman_dict = {'I':1, 'V':5, 'X':10, 'L':50, 'C':100, 'D':500, 'M':1000}
    int_equ = 0

    for i in range(len(s)):
        if i > 0 and roman_dict[s[i]] > roman_dict[s[i-1]]:
            int_equ += roman_dict[s[i]] - 2*roman_dict[s[i-1]]
        else:
            int_equ += roman_dict[s[i]]

    return int_equ
Shyambeer Singh
  • 318
  • 2
  • 9
-3

try this:

def translate(string):
    values = {"i":1, "v":5, "x":10, "l":50, "c":100, "m":1000}
    return sum(map(lambda x: values[x], string))

The lambda stands for a one-line function. That's why they are called anonymous functions. You don't have to define them outide using def and all that formality.

You can type something like this on the shell:

f = lambda x: x + 3 f(3) 6 or f = lambda x,y: x + y f("foo", "bar") 'foobar'

Im using map to apply my new-born function into every element of a iterable. In this case, the iterable is one string like "mclvii". Doing it i generated a list where each value its his rersepective value. See a lambda example to calculate squares:

>>> a = [1,2,3,4]
>>> square = lambda x: x**2
>>> l = map(square, a)
>>> l = [1,4,9,16]

So, it's lambda when you need a function on the fly, and map when you want to apply a function to all elements in a list.

Now a example using recursion:

def translate2(string):
    values = {"i":1, "v":5, "x":10, "l":50, "c":100, "m":1000}
    if not string:
        return 0
    return values[string[0]] + translate2(string[1:])
Lucas Ribeiro
  • 6,132
  • 2
  • 25
  • 28
  • 1
    Using lambda functions here just makes things more complicated. If you use a listcomp instead of map, you can just use the expression directly. If you really want a function, it already exists in operator, so you don't need to recreate it. – abarnert Oct 11 '13 at 20:30
  • 1
    Wrong Answer. Please delete this – DollarAkshay Jan 31 '19 at 12:26