This is now my current code after what user2486 said.
def romanMap():
map=((\"M\", 1000),(\"CM\", 900),(\"D\", 500),(\"CD\", 400),(\"C\", 100),(\"XC\"
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))