用 Tkinter 实现一个罗马数字转整数的简单工具

背景

TkDocs tutorial 里介绍了 Tkinter\text{Tkinter} Tkinter,其中有 A First (Real) Example 一文,这篇文章里有一个使用 Tkinter\text{Tkinter} Tkinter 生成图形化界面的简单例子。我想在那篇文章的基础上实战一下,于是想到可以写一个正整数和罗马数字互相转化的简易工具。用 Tkinter 实现一个简单的罗马数字转化工具 一文中已经提供了可以将正整数转化为罗马数字的代码,本文会探讨如何进行将罗马数字转化为正整数。

正文

如何将罗马数字转化为正整数

转化规则可以参考 13. 罗马数字转整数 这道题。以 MMMXCIV\text{MMMXCIV} MMMXCIV 为例,如果需要手动转化的话,可以这样分析:将 MMMXCIV\text{MMMXCIV} MMMXCIV 视为以下三部分

  • MMM\text{MMM} MMM:表示 30003000 3000
  • XC\text{XC} XC:表示 9090 90
  • IV\text{IV} IV:表示 44 4

因此 MMMXCIV\text{MMMXCIV} MMMXCIV 对应的整数是 3000+90+4=30943000+90+4=3094 3000+90+4=3094。用代码来处理时,不必这么做,可以简单些 ⬇️

python 复制代码
def to_int(roman):
    mapping = {
        "I": 1,
        "V": 5,
        "X": 10,
        "L": 50,
        "C": 100,
        "D": 500,
        "M": 1000
    }
    result = 0
    prev_value = 0
    for char in roman:
        curr_value = mapping[char]
        result += curr_value
        if prev_value != 0 and prev_value < curr_value:
            result -= prev_value * 2
        prev_value = curr_value
    return result

检查输入是否合法

上一小节提供的 to_int 方法可以将合法的罗马数字转化为对应的正整数。但是我们也要考虑到用户的输入可能非法的情况,例如 IIMMM\text{IIMMM} IIMMM 按照 to_int 方法的逻辑,会被处理成 30023002 3002,但是 IIMMM\text{IIMMM} IIMMM 不是合法的罗马数字( 30023002 3002 对应的罗马数字是 MMMII\text{MMMII} MMMII)。用 Tkinter 实现一个简单的罗马数字转化工具 一文提到,我们把整数转化为罗马数字时,可以分别处理千位/百位/十位/个位,这几位是独立的。以个位为例,如果个位不是 00 0 的话,那么合法的情况只有以下 99 9 种

个位上的数字 对应的罗马数字
11 1 I\text{I} I
22 2 II\text{II} II
33 3 III\text{III} III
44 4 IV\text{IV} IV
55 5 V\text{V} V
66 6 VI\text{VI} VI
77 7 VII\text{VII} VII
88 8 VIII\text{VIII} VIII
99 9 IX\text{IX} IX

那么对应的正则表达式可以写成 I|II|III|IV|V|VI|VII|VIII|IX\text{I|II|III|IV|V|VI|VII|VIII|IX} I|II|III|IV|V|VI|VII|VIII|IX。但是这样写太暴力了,可以将 1,2,31,2,3 1,2,3 的情况合并,将 5,6,7,85,6,7,8 5,6,7,8 的情况合并。合并后变为 I{1,3}|IV|VI{0,3}|IX\text{I\{1,3\}|IV|VI\{0,3\}|IX} I{1,3}|IV|VI{0,3}|IX,再加上个位为 00 0 的情况,正则表达式变为 ⬇️
I{0,3}|IV|VI{0,3}|IX\text{I\{0,3\}|IV|VI\{0,3\}|IX} I{0,3}|IV|VI{0,3}|IX

用类似的思路进行分析,可以得出合法罗马数字的十位部分满足下方的正则表达式
X{0,3}|XL|LX{0,3}|XC\text{X\{0,3\}|XL|LX\{0,3\}|XC} X{0,3}|XL|LX{0,3}|XC

用类似的思路进行分析,可以得出合法罗马数字的百位部分满足下方的正则表达式
C{0,3}|CD|DC{0,3}|CM\text{C\{0,3\}|CD|DC\{0,3\}|CM} C{0,3}|CD|DC{0,3}|CM

合法罗马数字的千位部分满足下方的正则表达式
M{0,3}\text{M\{0,3\}} M{0,3}

将以上结果合并在一起,正则表达式变为 ⬇️
M{0,3}(C{0,3}|CD|DC{0,3}|CM)(X{0,3}|XL|LX{0,3}|XC)(I{0,3}|IV|VI{0,3}|IX)\text{M\{0,3\}(C\{0,3\}|CD|DC\{0,3\}|CM)(X\{0,3\}|XL|LX\{0,3\}|XC)(I\{0,3\}|IV|VI\{0,3\}|IX)} M{0,3}(C{0,3}|CD|DC{0,3}|CM)(X{0,3}|XL|LX{0,3}|XC)(I{0,3}|IV|VI{0,3}|IX)

另外还需要排除掉输入字符串为空(即,"")的情况。

完整的代码

A First (Real) Example 一文中有使用 Tkinter\text{Tkinter} Tkinter 生成图形化界面的简单例子。在它的基础上,可以写出以下 Python3\text{Python3} Python3 代码

python 复制代码
from tkinter import *
from tkinter import ttk
import re

def convert():
    try:
        raw = roman.get().strip()
        if raw == "":
            result.set("无效输入")
            return
        if re.match("^(M{0,3})(C{0,3}|CD|DC{0,3}|CM)(X{0,3}|XL|LX{0,3}|XC)(I{0,3}|IV|VI{0,3}|IX)$", raw):
            result.set(to_int(raw))
        else:
            result.set("无效输入")
            return
    except ValueError:
        pass

def to_int(roman):
    mapping = {
        "I": 1,
        "V": 5,
        "X": 10,
        "L": 50,
        "C": 100,
        "D": 500,
        "M": 1000
    }
    result = 0
    prev_value = 0
    for char in roman:
        curr_value = mapping[char]
        result += curr_value
        if prev_value != 0 and prev_value < curr_value:
            result -= prev_value * 2
        prev_value = curr_value
    return result


root = Tk()
root.title("罗马数字转整数小工具")

mainframe = ttk.Frame(root, padding=(3, 3, 12, 12))
mainframe.grid(column=0, row=0, sticky=(N, W, E, S))

roman = StringVar()
roman_entry = ttk.Entry(mainframe, width=12, textvariable=roman)
roman_entry.grid(column=2, row=1, sticky=(W, E))

ttk.Button(mainframe, text="转化为整数", command=convert).grid(column=1, row=2, sticky=W)

ttk.Label(mainframe, text="请输入一个罗马数字").grid(column=1, row=1, sticky=W)

result = StringVar()
ttk.Label(mainframe, textvariable=result).grid(column=2, row=2, sticky=W)

root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
mainframe.columnconfigure(2, weight=1)
for child in mainframe.winfo_children(): 
    child.grid_configure(padx=5, pady=5)

roman_entry.focus()
root.bind("<Return>", convert)

root.mainloop()

运行

请将上一小节展示的完整代码保存为 from_roman.py。使用如下命令可以运行 from_roman.py

bash 复制代码
python3 from_roman.py

运行效果如下

我们输入一个合法的罗马数字,例如 MCMXCVII\text{MCMXCVII} MCMXCVII,然后点击"转化为罗马数字"按钮,效果如下

再用其他罗马数字验证一下(例如 MMMXCIV\text{MMMXCIV} MMMXCIV),效果如下

运行结果符合预期

参考资料

相关推荐
Vec‑Jie4 分钟前
MerchantOps-KBQA 实践(十四):本地运行、性能限制与可验证的交付边界
人工智能·后端·python·flask
qo_tn4 分钟前
微信机器人-webhook技术文档_02-部署前准备与环境规划
后端
雪隐7 分钟前
WPF + MVVM 实战系列02-告别 INPC 手写时代,做个体面的现代 WPF 人
前端·后端·c#
霸道流氓气质16 分钟前
SpringBoot中使用OAuth2 认证与 JWT Token — 概念、原理与实践
java·spring boot·后端
bug嘛我经常写17 分钟前
dbf文件UTF-8转GBK编码,以及两编码文件互转
数据库·python
dogstarhuang18 分钟前
Kimi K3 本地部署实战:从 1.56TB 权重到推理服务的完整成本分析
java·人工智能·后端·ai·开源·接口·程序员创富
胡耀超21 分钟前
AI出事后,怎么查?——读《AI Forensics》
人工智能·python·数字取证·ai取证·
星栈28 分钟前
我以为 TS7.0 只是换个版本号,结果编译快了 9 倍,也踩了 5 个坑
后端·typescript·node.js
梦想三三33 分钟前
Python从零实现AI Agent电商客服(Qwen/Ollama+SQLite源码解析)
人工智能·python·sqlite
Csvn36 分钟前
📊 SQL 入门 Day 15:日期与字符串函数 — SQL 中的数据处理瑞士军刀
后端·sql