zoukankan      html  css  js  c++  java
  • 四则运算自动生成器实现(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()  # 进入消息循环
    View Code

     

    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
    View Code

    3、改进与总结

       界面可以进行进一步地美化,比如可以从字体、文本框、背景进行改进。该项目GUI的设计难点在于计算界面中,N条算式的输出和判断,这是一个相对动态的过程:输出一个算式——>用户输入答案,回车确定——>输出计算结果——>输出下一条算式,直到输出完用户指定的题目数。另外,由于“计算界面”输出的均是静态文本,界面范围限制了最大的可输出题目。可进行进一步改进,比如设计成可滑动界面。

  • 相关阅读:
    Windows 代码实现关机(直接黑屏)
    Windows SEH学习 x86
    Smali 语法文档
    SIOCADDRT: No such process
    Windbg 常用命令整理
    ida GDB 远程调试
    IDA 使用技巧
    Windows X64 Patch Guard
    C 和C++ 名称修饰规则
    【转载】 硬盘主引导记录(MBR)及其结构详解
  • 原文地址:https://www.cnblogs.com/nicefurmine/p/15313302.html
Copyright © 2011-2022 走看看