四则运算自动生成器实现(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条算式的输出和判断,这是一个相对动态的过程:输出一个算式——>用户输入答案,回车确定——>输出计算结果——>输出下一条算式,直到输出完用户指定的题目数。另外,由于“计算界面”输出的均是静态文本,界面范围限制了最大的可输出题目。可进行进一步改进,比如设计成可滑动界面。

posted @ 2021-09-20 00:22  玩的三立方  阅读(762)  评论(0编辑  收藏  举报