四則運算自動生成器實現(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條算式的輸出和判斷,這是一個相對動態的過程:輸出一個算式——>用戶輸入答案,回車確定——>輸出計算結果——>輸出下一條算式,直到輸出完用戶指定的題目數。另外,由於“計算界面”輸出的均是靜態文本,界面范圍限制了最大的可輸出題目。可進行進一步改進,比如設計成可滑動界面。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM