使用字典将Python数字转换为整数转换器

Roman Numerals to Integers Converter with Python using a dictionary

我正在学习编码,我做了一个练习,把罗马数字转换成整数。我知道写这个程序有很多不同的方法,但如果你能帮我找出错误,我会很感激的。我很想听听关于如何写得更好的建议,但我现在真正能使用的是理解我对这本书的错误之处。

我有一本叫roman_numerals的字典。它的keys是罗马数字值,它的values是它们的匹配整数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
roman_numerals = {"M":1000,"CM":900,"D":500,"CD":400,"C":100,"XC":90,"L":50,"XL":40,"X":10,"V":5,"IV":4,"I":1}

def roman_int(user_choice):
    if user_choice =="1":
        user_roman = input("What numeral would you like to convert?
"
).upper()
        resultI = 0
        for k,v in roman_numerals.items():          
            if user_roman == k:
                resultI += roman_numerals.get(user_roman)
            else:
                for i in user_roman:
                    if i in roman_numerals.keys():
                        if i == k:
                            resultI += v
    print(resultI)

当我运行代码时,如果我使用的数字等于一个以上字符的key(例如"i v"),我得到的结果是"iv"和"v"的相加。或"cm"和"m"。我知道为什么会发生,因为我要求迭代。但是,当程序返回值时,我可以要求它停止迭代吗?我觉得我真的很接近解决方案,但我只是在这一点上感到困惑。

谢谢您!


最好将值表示为元组列表,因为这样可以定义其中的顺序,因此,如果字符串在该点包含'IX',我们可以避免匹配'I'。因此,我们可以将转换定义为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
roman_numerals = [
    ('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)
]

请注意,您忘记使用IX,这在某种程度上造成了问题,因为它将IX解释为11

现在我们可以通过每次执行str.startswith()python doc〕检查来处理字符串,从找到前缀开始,添加相应的值,并推进字符串的偏移量,例如:

1
2
3
4
5
6
7
8
9
10
11
12
def roman_int(user_choice):
    ix = 0
    result = 0
    while ix < len(user_choice):
        for k, v in roman_numerals:
            if user_choice.startswith(k, ix):
                result += v
                ix += len(k)
                break
        else:
            raise ValueError('Invalid Roman number.')
    return result

因此,我们通过字符串进行枚举,并且每次查找匹配的罗马数字。例如:

1
2
3
4
5
6
>>> roman_int('MCMXC')
1990
>>> roman_int('MCMXCIII')
1993
>>> roman_int('MMXVIII')
2018

例如,如果输入无效字符,系统也会出错:

1
2
3
4
5
>>> roman_int('MMXQVIII')
Traceback (most recent call last):
  File"<stdin>", line 1, in <module>
  File"<stdin>", line 11, in roman_int
ValueError: Invalid Roman number.

不过,上述方法并不十分有效:每次我们枚举roman_literals,直到找到匹配项。但是,一旦我们处理了"五十年代"(L),我们就知道再也不会分析"千年"(M)。我们可以通过记住roman_numerals中的指数来提高绩效:

1
2
3
4
5
6
7
8
9
10
11
12
13
def roman_int(user_choice):
    ix = 0
    iy = 0
    result = 0
    while ix < len(user_choice):
        while iy < len(roman_numerals) and not user_choice.startswith(roman_numerals[iy][0], ix):
            iy += 1
        if iy < len(roman_numerals):
            result += roman_numerals[iy][1]
            ix += len(roman_numerals[iy][0])
        else:
            raise ValueError('Invalid Roman numeral')
    return result

这再次产生了我们预期的结果:

1
2
3
4
5
6
7
8
9
10
>>> roman_int('MDCCLXXVI')
1776
>>> roman_int('MCMLIV')
1954
>>> roman_int('MCMXC')
1990
>>> roman_int('MMXIV')
2014
>>> roman_int('MMXVIII')
2018

但也更严格,例如,CMM不是有效的罗马数字,而MCM是:

1
2
3
4
5
6
7
>>> roman_int('CMM')
Traceback (most recent call last):
  File"<stdin>", line 1, in <module>
  File"<stdin>", line 12, in roman_int
ValueError: Invalid Roman numeral
>>> roman_int('MCM')
1900


您必须确保使用了构成总和的所有字符。由于所有多字符的"原子"字都以较低值的单位开始,否则,较高值的单位首先出现,因此一个简单的贪婪方法是可行的:

  • 尝试将前两个字符转换为一个整体,如果不可能,请转换第一个单个字符。
  • 向前移动适当数量的步骤。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    def roman_int(user_roman):
        user_roman = user_roman.upper()
        resultI = 0

        while user_roman:
            # try first two chars
            if user_roman[:2] in roman_numerals:
                resultI += roman_numerals[user_roman[:2]]
                # cut off first two chars
                user_roman = user_roman[2:]
            # try first char
            elif user_roman[:1] in roman_numerals:
                resultI += roman_numerals[user_roman[:1]]
                # cut off first char
                user_roman = user_roman[1:]
            else:
                print('No roman number')
                return
        print(resultI)


不需要循环查字典。我的代码稍有不同,但我已经尽力保留尽可能多的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
roman_numerals =  {"M":1000,"CM":900,"D":500,"CD":400,"C":100,"XC":90,"L":50,"XL":40,"X":10,"V":5,"IV":4,"I":1}

def roman_int(user_choice):
    if user_choice =="1":
        user_roman = input("What numeral would you like to convert?
"
).upper()
        result = 0
        values = []

        # return the result if the input is in the dictionary
        try:
            result = roman_numerals[user_roman]
        except KeyError:
            # split up the user input and convert each character into corresponding numeral
            for i in user_roman:
                try:
                    value = roman_numerals[i]
                    values.append(value)
                # if user enters character not used in roman numerals
                except KeyError:
                    print("Not valid input")
            # loop through all values and add them up
            for i, value in enumerate(values):
                try:
                    # if a value is larger than the next value, add it
                    if value > values[i+1]:
                        result += value
                    # else the number is obtained by substracting the smaller value from the larger value
                    else:
                        actual_value = values[i+1] - value
                        result = result + actual_value
                        #set the next number to 0 as it has already been added
                        values[i+1] = 0
                except IndexError:
                    # this try except block catches the IndexError exception caused when i+1 > len(values)
                    pass
        print(result)


计算字符串中每个数字的出现次数,而不是检查字符串的出现次数,然后只需删除出现双字母(如CM)的出现次数,这样就可以删除CM*值CM的出现次数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
roman_numerals = {"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}

def roman_int(user_choice):
    if user_choice =="1":
        result = 0
        user_roman = input("What numeral would you like to convert?
"
).upper()
        for k,v in roman_numerals.items():          
            result += v * user_roman.count(k)
            if len(k) == 2:
                result -= roman_numerals[k[0]] * user_roman.count(k)
                result -= roman_numerals[k[1]] * user_roman.count(k)                
    print(result)

roman_int("1")

另一种看问题的方法,我想

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
roman_numerals = {"M":1000,"CM":900,"D":500,"CD":400,"C":100,"XC":90,"L":50,"XL":40,"X":10,"V":5,"IV":4,"I":1}

def roman_int(user_choice):
    if user_choice =="1":
        user_roman = input("What numeral would you like to convert?
"
).upper()
        resultI = 0
        pos = 0
        try:
            resultI = roman_numerals[user_roman]
        except:
            try:
                while pos < len(user_roman):
                    resultI += roman_numerals[user_roman[pos]]
                    pos+=1
            except:
                print('Not present in our dictionary')
    print(resultI)