四则运算自动生成器实现(python、wxpython、GUI)
四则运算自动生成器(python wxpython GUI)
1、项目概述
1.1 项目要求
·自动生成四则运算题目,实时判断正误
·支持真分数的四则运算
1.2 GUI界面呈现
本文借鉴一名作者(昵称:步平凡)的项目代码【博客园、GitHub】。具体的解决思路和设计过程,在其GitHub上有详细介绍。以下是部分运行结果截图:
可以看见,该作者的输入和输出均在命令行实现。所以,小编尝试将该程序用GUI界面呈现。
1.3 界面设计效果图
对程序进行修改后,以下为修改后的运行截图。【使用wxpython库】
主要分为两个Frame,“四则运算生成器” 和 “计算界面”。
大体每个Frame所包含的插件如下表:
四则运算生成器 | 5个静态文本(显示相关需求参数) |
5个文本框(获得需求参数) | |
1个按钮(响应“计算界面”事件) | |
计算界面 | 静态文本(显示题目及显示用户答题结果) |
文本框(获得用户答案,回车响应) |
2、GUI界面设计(main.py)及对四则运算功能的设计(formula.py)
2.1 main.py
详细代码
# -*- coding: utf-8 -*- ''' * @Author : 玩的三立方 * @Date : 2021-09-20 * @Description : 生成算式 * @LastEditTime : * ''' # 导入wx模块 import wx from wx.core import CLEAR from formula import OPT, GeneralFormular, ComputeFormular class MyApp(wx.App): def OnInit(self): frame = wx.Frame(parent=None, title="四则运算生成器") # 新建框架 panel = wx.Panel(frame, -1) # 生成面板 Text = wx.StaticText(panel,-1,"请输入相关需求",pos=(150,2)) label1=wx.StaticText(panel,-1,"生成算式数量(>=1)",pos=(10,30)) #标签 text1=wx.TextCtrl(panel,-1,pos=(200,30),size=(180,20), #输入框 style=wx.TE_MULTILINE) label2=wx.StaticText(panel,-1,"每个数值的上限(>=10)",pos=(10,60)) text2=wx.TextCtrl(panel,-1,pos=(200,60),size=(180,20), style=wx.TE_MULTILINE) label3=wx.StaticText(panel,-1,"操作数个数(>=2)",pos=(10,90)) text3=wx.TextCtrl(panel,-1,pos=(200,90),size=(180,20), style=wx.TE_MULTILINE) label4=wx.StaticText(panel,-1,"运算符种数(1~4)",pos=(10,120)) text4=wx.TextCtrl(panel,-1,pos=(200,120),size=(180,20), style=wx.TE_MULTILINE) label5=wx.StaticText(panel,-1,"是否要包含分数(0,1)",pos=(10,150)) text5=wx.TextCtrl(panel,-1,pos=(200,150),size=(180,20), style=wx.TE_MULTILINE) button = wx.Button(panel,-1, '确定', pos=(150, 180)) # 确定按钮位置 self.text1=text1 #方便跨函数调用 self.text2=text2 self.text3=text3 self.text4=text4 self.text5=text5 self.button1 = button self.Bind(wx.EVT_BUTTON, # 绑定事件,如果是按钮被点击 self.GetInPut, # 激发的按钮事件 self.button1) # 激发的按钮 frame.Center() frame.Show() # 显示 return True def GetInPut(self,event): #事件的激发函数,获得输入 while True: n = self.text1.GetValue() try: n = abs(int(n)) if n < 1: wx.MessageBox("生成算式数量-[Eror]: Input illegal! Please input again... "\ , "INFO", wx.OK|wx.ICON_INFORMATION) break except Exception as e: wx.MessageBox("生成算式数量-[Eror]: Input illegal! Please input again... "\ , "INFO", wx.OK|wx.ICON_INFORMATION) break up_limit = self.text2.GetValue() try: up_limit = abs(int(up_limit)) if up_limit < 10: wx.MessageBox("每个数值的上限-[Eror]: Input illegal! Please input again... "\ , "INFO", wx.OK|wx.ICON_INFORMATION) break except Exception as e: wx.MessageBox("每个数值的上限-[Eror]: Input illegal! Please input again... "\ , "INFO", wx.OK|wx.ICON_INFORMATION) break oper_num = self.text3.GetValue() try: oper_num = abs(int(oper_num)) if oper_num < 2: wx.MessageBox("操作数个数-[Eror]: Input illegal! Please input again... "\ , "INFO", wx.OK|wx.ICON_INFORMATION) break except Exception as e: wx.MessageBox("操作数个数-[Eror]: Input illegal! Please input again... "\ , "INFO", wx.OK|wx.ICON_INFORMATION) break oper_variety = self.text4.GetValue() try: oper_variety = abs(int(oper_variety)) if oper_variety < 1 or oper_variety > 4: wx.MessageBox("运算符种数-[Eror]: Input illegal! Please input again... "\ , "INFO", wx.OK|wx.ICON_INFORMATION) break except Exception as e: wx.MessageBox("运算符种数-[Eror]: Input illegal! Please input again... "\ , "INFO", wx.OK|wx.ICON_INFORMATION) break has_fraction = self.text5.GetValue() try: has_fraction = abs(int(has_fraction)) if has_fraction != 0 and has_fraction != 1: wx.MessageBox("是否要包含分数-[Eror]: Input illegal! Please input again... "\ , "INFO", wx.OK|wx.ICON_INFORMATION) break except Exception as e: wx.MessageBox("是否要包含分数-[Eror]: Input illegal! Please input again... "\ , "INFO", wx.OK|wx.ICON_INFORMATION) break self.n = int(n) self.up_limit = int(up_limit) self.oper_num = int(oper_num) self.oper_variety = int(oper_variety) self.has_fraction = int(has_fraction) self.solveFormular() break def solveFormular(self): opt = OPT(self.up_limit, self.oper_num, self.oper_variety, self.has_fraction) gf = GeneralFormular(opt) cf = ComputeFormular() formulars = [] results = [] for i in range(int(self.n)): f = gf.solve() formulars.append(" ".join(i for i in f) + " = ") results.append(cf.solve(f)) self.formulars=formulars self.results=results self.displayFormular() def displayFormular(self): frame = wx.Frame(parent=None, title="计算界面", size=(500,500)) self.panel = wx.Panel(frame, -1) # 生成面板 self.Column = 30 self.Row = 10 self.N =len(self.formulars) self.i = 0 self.GetResult() frame.Center() frame.Show() # 显示 return True def GetResult(self): #显示题目和答题文本框 if self.Column >= 800: self.Row += 500 self.Column = 30 T_label=wx.StaticText(self.panel,-1,"第{}题:".format(self.i+1) + self.formulars[self.i],pos=(self.Row,self.Column)) #标签 T_text=wx.TextCtrl(self.panel,-1,pos=(self.Row+340,self.Column),size=(100,20), #输入框 style=wx.TE_PROCESS_ENTER) self.T_text = T_text self.Bind(wx.EVT_TEXT_ENTER, self.judgeResult, T_text) def judgeResult(self,event): #判断正误 self.result = self.T_text.GetValue() self.Column += 30 if self.result == self.results[self.i]: flag = "正确 √ √ √" else: flag = "错误❌❌❌" T_label=wx.StaticText(self.panel,-1," 正确答案:{} 回答{}"\ .format(self.results[self.i], flag),pos=(self.Row,self.Column)) self.i += 1 try: if self.i <= self.N-1: self.Column = self.Column+30 self.GetResult() else: End_label=wx.StaticText(self.panel,-1," ---------答题结束--------- ",pos=(self.Row, self.Column+50)) End_label.SetFont(wx.Font(12, wx.SWISS)) except Exception as e: return True if __name__ == '__main__': app = MyApp() # 启动 app.MainLoop() # 进入消息循环
2.2 formula.py
详细代码
import random import datetime import argparse import re from fractions import Fraction def OPT(up_limit=10, oper_num=2, oper_variety=4, has_fraction=True, be_negetive=False): ''' * 设置参数 * @param up_limit {int} 操作数数值上限 * @param oper_num {int} 操作数个数 * @param oper_variety {int} 运算符种类 * @param has_fraction {bool} 是否带有分数 * @param be_negative {bool} 可否存在负数 ''' parse = argparse.ArgumentParser() # 操作数数值上限 parse.add_argument('--up_limit', type=int, default=up_limit) # 操作数个数 parse.add_argument('--oper_num', type=int, default=oper_num) # 运算符种类 parse.add_argument('--oper_variety', type=int, default=oper_variety) # 是否带有分数 parse.add_argument('--has_fraction', type=bool, default=has_fraction) # 可否存在负数 parse.add_argument('--be_negative', type=bool, default=be_negetive) return parse.parse_args(args=[]) class GeneralFormular: ''' * 生成算式 * @param opt {OPT} 参数 ''' def __init__(self, opt): self.opt = opt # 定义运算符 self.operator = ['+', '-', '×', '÷'] # @profile def catFormula(self, operand1, operator, operand2): ''' * 连接算式 * @param operand1 {str} 操作数1 * @param opertor {str} 运算符 * @param operand2 {str} 操作数2 * @return {str} ''' return "{}#{}#{}".format(operand1, operator, operand2) # @profile def getRandomIntOperand(self): ''' * 返回随机整数操作数 * @return {int} ''' return random.randint(0, self.opt.up_limit) # @profile def getRandomFractionOperand(self): ''' * 返回随机分数操作数 * @return {str} ''' # 生成两个整数,一个作为分子,一个作为分母 num01 = self.getRandomIntOperand() num02 = self.getRandomIntOperand() while num01 == num02 or num02==0: num02 = self.getRandomIntOperand() while num01 == 0: num01 = self.getRandomIntOperand() # 保证分数为真分数, 化简 if num01 < num02: return Fraction(num01, num02).__str__() else: return Fraction(num02, num01).__str__() # @profile def getRandomOperator(self): ''' * 返回随机运算符 * @return {str} ''' index = random.randint(0, self.opt.oper_variety-1) return self.operator[index] # @profile def getOriginFormular(self): ''' * 生成整数源算式 * @return {list} ''' # 通过self.opt.oper_num控制操作数个数,循环调用catFormula()方法构造算式 formular = self.getRandomIntOperand() for i in range(self.opt.oper_num-1): formular = self.catFormula(formular, self.getRandomOperator(), self.getRandomIntOperand()) # 去掉'÷0' while(True): if '÷#0' in formular: formular = formular.replace('÷#0', '÷#' + str(self.getRandomIntOperand())) else: break # 通过'#'分割生成列表 formular_list = formular.split('#') return formular_list # @profile def insertBracket(self, formular_list): ''' * 插入括号 * @param formular_list {list} 源算式列表 * @return {list} ''' # print(formular) # 若只包含+号 或 只有两个操作数 则不用加括号 if self.opt.oper_variety <= 2 or self.opt.oper_num == 2: return formular_list # 若不包含×÷ 则不用加括号 if '×' not in formular_list and '÷' not in formular_list: return formular_list # 存储添加括号的算式 new_formular_list = [] # flag表示是否已存在左括号,作用在于构造出一对括号 flag = 0 # 添加括号 while(len(formular_list) > 1): oper = formular_list.pop(1) # 若下一个符号为 + or - , 则插入括号 if oper == '-' or oper == '+': if flag == 0: new_formular_list.append("(") flag = 1 new_formular_list.append(formular_list.pop(0)) new_formular_list.append(oper) else: new_formular_list.append(formular_list.pop(0)) if flag == 1: new_formular_list.append(")") flag = 0 new_formular_list.append(oper) # print(operand_list, operator_list, new_formular) new_formular_list.append(formular_list.pop(0)) if flag == 1: new_formular_list.append(")") return new_formular_list # @profile def replaceFraction(self, formular_list): ''' * 带入分数 * @param formular_list {list} 源算式列表,可能包含括号 * @return {list} ''' # 带入分数个数 fraction_num = 1 if self.opt.oper_num > 2: fraction_num = (self.opt.oper_num - 1) / 2 index = random.randint(0, len(formular_list)-1) interval = 1 while True: if formular_list[index].isdigit(): break elif formular_list[index - interval].isdigit(): index -= interval break elif formular_list[index + interval].isdigit(): index += interval break else: interval += 1 formular_list[index] = self.getRandomFractionOperand() return formular_list # @profile def solve(self): ''' * 整合生成算式的后缀表达式,带括号 * @return {list} ''' # 生成原生算式 ori_formular = self.getOriginFormular() # 添加括号 bra_formular = self.insertBracket(ori_formular) # 带入分数 if self.opt.has_fraction: bra_formular = self.replaceFraction(bra_formular) return bra_formular class ComputeFormular: ''' * 计算算式的值 ''' def __init__(self): pass # @profile def getPostFormular(self, formular_list): ''' * 中缀表达式转为后缀表达式 * @param formular_list {list} 中缀表达式 * @return {list} ''' # 运算符优先级 priority = {'×': 3, '÷': 3, '+': 2, '-': 2, '(': 1} # 运算符栈 operator_stack = [] # 后缀表达式 post_formular_list = [] # 中缀表达式转为后缀表达式 while formular_list: char = formular_list.pop(0) if char == '(': operator_stack.append(char) elif char == ')': oper_char = operator_stack.pop() while oper_char != '(': post_formular_list.append(oper_char) oper_char = operator_stack.pop() elif char in '+-×÷': while operator_stack and priority[operator_stack[-1]] >= priority[char]: post_formular_list.append(operator_stack.pop()) operator_stack.append(char) else: post_formular_list.append(char) # 若符号栈不为空则循环 while operator_stack: post_formular_list.append(operator_stack.pop()) # print(post_formular) return post_formular_list # @profile def compute(self, char, num01, num02): ''' * 计算算式的值 * @param char {str} 运算符 * @param num01 {str} 第二个数字,可能为分数 * @param num02 {str} 第二个数字,可能为分数 * @return {str} ''' if char == '+': return (Fraction(num02) + Fraction(num01)).__str__() elif char == '-': return (Fraction(num02) - Fraction(num01)).__str__() elif char == '×': return (Fraction(num02) * Fraction(num01)).__str__() elif char == '÷': try: return (Fraction(num02) / Fraction(num01)).__str__() except Exception as e: # print("Error: ", e) return "NaN" # @profile def calcFormular(self, post_formular_list): ''' * 计算算式的值 * @param post_formular_list {list} 后缀表达式 * @return {str} ''' # 操作数栈 operand_stack = [] while post_formular_list: char = post_formular_list.pop(0) if char in '+-×÷': result = self.compute(char, operand_stack.pop(), operand_stack.pop()) if result == "NaN": return result operand_stack.append(result) else: operand_stack.append(char) return operand_stack.pop() # @profile def solve(self, formular): ''' * 整合计算中缀表达式的值 * @param formular {list} 后缀表达式 * @return {str} ''' # 转为后缀表达式 post_formular = self.getPostFormular(formular) # 计算值 value = self.calcFormular(post_formular) return value
3、改进与总结
界面可以进行进一步地美化,比如可以从字体、文本框、背景进行改进。该项目GUI的设计难点在于计算界面中,N条算式的输出和判断,这是一个相对动态的过程:输出一个算式——>用户输入答案,回车确定——>输出计算结果——>输出下一条算式,直到输出完用户指定的题目数。另外,由于“计算界面”输出的均是静态文本,界面范围限制了最大的可输出题目。可进行进一步改进,比如设计成可滑动界面。