Reputation: 357
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
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
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
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
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
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
Reputation: 9
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
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
Reputation: 5831
Right-to-left solution that is a bit more Pythonic (no indexes) and relatively short.
Algorithm:
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
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
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
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
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
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
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
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
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
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