Jake
Jake

Reputation: 357

Converting Roman Numerals to integers in python

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

Upvotes: 19

Views: 73941

Answers (17)

Brōtsyorfuzthrāx
Brōtsyorfuzthrāx

Reputation: 4749

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.

Upvotes: 33

R.K
R.K

Reputation: 1839

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 :)

Upvotes: 2

Shyambeer Singh
Shyambeer Singh

Reputation: 328

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

Upvotes: 0

r366y
r366y

Reputation: 381

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

Upvotes: 11

duhaime
duhaime

Reputation: 27611

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

Upvotes: 6

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)

Upvotes: 0

Mohan Meruva
Mohan Meruva

Reputation: 322

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

Upvotes: 0

Harvey
Harvey

Reputation: 5831

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))

Upvotes: 4

Gatis Seja
Gatis Seja

Reputation: 87

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

Upvotes: 0

runitfirst
runitfirst

Reputation: 323

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

Upvotes: 1

Vishav Vikram Kapoor
Vishav Vikram Kapoor

Reputation: 11

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

Upvotes: 0

Valentin Shergin
Valentin Shergin

Reputation: 7344

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

Upvotes: 3

Vicks
Vicks

Reputation: 1

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

Upvotes: 0

pepr
pepr

Reputation: 20794

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).

Upvotes: 0

Lucas Ribeiro
Lucas Ribeiro

Reputation: 6292

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:])

Upvotes: -3

Peacemaker636
Peacemaker636

Reputation: 97

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.

Upvotes: 3

user2864740
user2864740

Reputation: 62003

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:]

Upvotes: 7

Related Questions