在Python中将罗马数字转换为整数

19

根据user2486的建议,这是我当前的代码。

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

我在使用 while ns: 时遇到了以下错误:UnboundLocalError: local variable 'ns' referenced before assignment。


3
这是一种繁琐且较为困难的方法,如果使用词典的话会更加高效。你可以参考已经编写好的功能代码来实现,链接为:http://svn.python.org/projects/python/branches/pep-0384/Doc/tools/roman.py - user1786283
9
我想象不出那些函数正在做你想让它们做的事情。 - roippi
2
@roippi -- 当然不是。如果是那样,提问者就不会来这里问问题了 :) - mgilson
1
@roippi 不,你可以发起一个关于用回收的土豆制造房屋的众筹活动,并利用这笔资金解决你的问题。 - kojiro
请不要对问题进行如此大的修改 - 而是创建一个新问题!无论如何,“UnboundLocalError”意味着变量尚未被分配并且已经尝试使用它!在这种情况下,缺少从用户输入到ns的赋值。还要注意1)如果len(ns)== 1,则ns [1]可能会引发异常;2)循环外的初始赋值firstNum / secondNum没有任何作用;3)总数应仅从firstNum / secondNum计算,因为ns [x]返回一个字符串;4)实现一个名为“numberOfNumeral”的函数,执行我建议的操作,并使用它;) - user2864740
显示剩余2条评论
17个回答

33

不必重复造轮子(除非你想这样做)。Python曾经附带一个转换器(因此您可以转到Python 3.4.1源代码并在此位置获取模块:/Python-3.4.1/Doc/tools/roman.py;或者按照评论中的某人所说使用pip进行安装;我还没有验证pip版本;无论如何,然后您可以执行以下操作):

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

如果您需要将数字转换为5000及以上的罗马数字,您需要编写一个新函数,并可能制作自己的字体来表示罗马数字上方的线条(但这只适用于某些数字。在4999处停止是一个非常好的主意)。
要转换为罗马数字,请使用roman.toRoman(myInt)
或者(仅用于转换为罗马数字),您可以在Python 3.9.2中执行此操作(由于缺乏文档,我只能部分理解;因此,我的所有参数可能都不正确;但它似乎可以工作;格式化程序已过时,因此请不要指望它会长时间存在)。
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"

实际上,之前的评论中有人链接了罗马数字模块使用的相同源代码,但我不认为他们提到它实际上是随Python一起提供的。现在似乎已经不再随Python一起提供,但在3.4.1版本中是有的。


15
我认为这个东西不是Python自带的,但如果你安装了pip,你可以使用pip install roman来安装它。 - Tor Klingberg
或者使用 easy_install roman,因为它实际上是 Python 自带的。 - melwil
1
如果想要大写字母,Case应该为“I”。 - Jo King
1
包含Python 3.4.2的roman.py直接链接。 - notpeter

11

罗马数字从左到右阅读,随着每个符号的值的加减而变化。

如果一个值比后面的值小,则将其减去。否则,将其加上。

例如,我们想要将罗马数字MCMLIV转换成阿拉伯数字:

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.

现在我们可以计算出这个数字:

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

参考: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

不适用于“II”,“III”,“VII”等(在这种情况下会给出0、-1和5) - Abzac
1
修复了。在第一次比较时缺少了一个等号。 - r366y

7

考虑下面这些伪代码和提示(其中一些是有效的Python代码,有些不是,但有注释)。

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

等等,为什么你从ns[1]开始而不是从ns[0]开始?除此之外,这是一个很好的框架让用户参考。 - abarnert
我需要定义firstNum/secondNum吗? - Jake
1
@Jake:在Python中没有单独的“声明”和“定义”——只需将一个值赋给一个变量即可。 - abarnert
好的,我的意思是 firstNum=ns([0]) secondNum=ns([1])。 - Jake
@Jake:你已经在循环的顶部给它们赋值了。在那之前给它们不同的值是没有帮助的。 - abarnert

5
一个不需要外部库的简洁版本:
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

有没有办法修复这个问题? 索引 1 0 2 0 3 0 4 0 5 0 .. 65 II 66 II 67 II 68 II 69 II 名称:0,长度:69,数据类型:对象 回溯(最近的调用最先显示): File "C:\pandas\data.py",第115行,在<module>中 rn_to_int(xAs) File "C:\pandas\data.py",第111行,在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",第5902行,在__getattr__中 return object.getattribute(self, name) AttributeError: 'Series'对象没有属性'lower' - Lod
1
看起来你正在尝试在Pandas系列上使用rn_to_int函数。你应该在字符串上运行它。 - duhaime
谢谢你的建议。我有一个数据框,其中的NaN值已经转换为0,并且得到了这个链接:https://onecompiler.com/python/3zhu254qe 有没有什么建议可以修改代码以忽略0呢?非常感谢。 - Lod
目前看起来这个修复方法有效:https://onecompiler.com/python/3zhtzvyeu,参考链接为:https://dev59.com/aqj1oIgBc1ULPQZF3ChA#69279029。 - Lod
1
太棒了!它按预期运行得非常好!再次感谢。保重! - Lod

4

以下是一种更加Pythonic(无需索引)且相对简短的从右到左的解决方案。

算法:

  • 将罗马数字反转并映射到数字列表中
  • 找出应该被减去的数字,然后求和列表

示例:

'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

输出:

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

注意:希望找到一种(简短,内联)方法来动态更改序列的符号。例如,(5,1,10) ==> (5,-1,10)
更新:在放弃之前,这是我得到的最接近的结果。它与上面的代码完全相同,但它使用itertools.tee()zip()生成先前值和当前值的对,以消除对状态变量的需求。对next(cur)的单个调用使该列表比prev短一个,这是我们需要找出是否添加或减去当前值的所有状态。
例子:
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)) 

代码:

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

3
这是我的解决方案:
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

roman_to_arabic 可能有误,输入 XIV 时给出的是 16,但应该是 14。 - chaitan94

3

好的,你现在所拥有的东西存在很多问题。

首先,你得到一堆 0 的原因是因为你从未退出 while string != "": 循环,也从未将整数添加到总和中。所以 total 一直都是零,并且不断被打印。我在你所发布的代码上进行了注释,帮助你理解正在发生的事情。

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在他们发布的解决方案中有一些好的评论,看看那些评论,以了解你做错的事情。

这里是Python(不幸的是2.7)代码,可以执行你给出的伪代码所说的操作。

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

请注意,您发布的伪代码是不正确的。请注意对于输入'IIV'它将执行什么操作。它将从1中减去1,然后加上5。但它应该从5中减去2。

2

我知道这是一篇旧文章,但我想添加3个解决方案来将罗马数字转换为数字。

解决方案1:(大约运行时间= 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

解决方案2:(大约运行时间=60毫秒)

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

解决方案3:(大约运行时间=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

有时候,最简单的解决方案是最好的 :)

1
这段代码怎么样?
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

为了帮助 OP 解决当前的问题,可以在回答中添加一些解释说明。 - ρяσѕρєя K
1
将罗马数字转换为十进制...使用更少的代码...还有什么需要解释的..! - runitfirst

0

这是LeetCode的一个问题。

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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接