利用Python實現四則運算表達式生成程序


一. 項目基本信息

項目成員:梁華超、林賢傑

項目倉庫:Github

二. PSP2.1表格

PSP2.1 Personal Software Process Stages 預估耗時(分鍾) 實際耗時(分鍾)
Planning 計划 20 25
· Estimate · 估計這個任務需要多少時間 20 25
Development 開發 1200 1740
· Analysis · 需求分析 (包括學習新技術) 40 55
· Design Spec · 生成設計文檔 40 41
· Design Review · 設計復審 (和同事審核設計文檔) 30 20
· Coding Standard · 代碼規范 (為目前的開發制定合適的規范) 30 31
· Design · 具體設計 40 66
· Coding · 具體編碼 1100 1520
· Code Review · 代碼復審 40 41
· Test · 測試(自我測試,修改代碼,提交修改) 60 64
Reporting 報告 70 100
· Test Report · 測試報告 20 24
· Size Measurement · 計算工作量 20 21
· Postmortem & Process Improvement Plan · 事后總結, 並提出過程改進計划 30 58
合計   1390 1963

三. 設計實現過程及代碼說明

項目文件結構如下:

模塊 功能
main.py 主函數
answer.py 計算答案和校對答案
exp_generate.py 表達式生成
suffix_expression.py 將中綴表達式轉成后綴表達式和求值

1.分析與設計

本設計涉及到的基本數據類型和表達式有棧,二叉樹,逆波蘭表達式(后綴表達式)

表達式生成 :

仔細分析表達式有如下特點:

  • 運算數的個數比運算符多一
  • 被除數不能為0
  • 兩個操作數不需要加括號

利用python中字符串列表來存儲四則表達式,新建一個列表,大小為運算符個數+運算數,然后循環遍歷此列表,在偶數位置插入隨機的運算數,在奇數位置插入隨機的運算符。

括號的插入:

左括號的插入位置是從0到操作數個數的一半之間的一個隨機數,右邊括號為左括號的位置+1到操作數個數的一半+1。

計算答案:

將中綴表達式轉為后綴表達式,再進行求值

生成的二叉樹如下這樣:

2.具體實現

(1) 表達式生成關鍵代碼

需要注意的是除號后面的運算符不能為0,如果生成的是0,即重新生成插入,直到生成不為0的運算符為止。

while i < exp_num:
            random_num_operation = randint(1, config.max_num_of_oper)
            is_need_parenteses = randint(0,1)
            number_of_oprand = random_num_operation + 1 #操作數比操作符的數目多1
            exp = []
            for j in range(random_num_operation + number_of_oprand):
                if j % 2 == 0:
                    #隨機生成操作數
                    exp.append(self.generate_operand(randint(0,3), config.num_range))
                    if j > 1 and exp[j-1] == '÷' and exp[j] == '0':
                        while True:
                            exp[j-1] = self.generate_operation()
                            if exp [j-1] == '÷':
                                continue
                            else:
                                break
                else:
                    #生成運算符
                    exp.append(self.generate_operation())
            
            #判斷是否要括號
            if is_need_parenteses and number_of_oprand != 2:
                expression = " ".join(self.generate_parentheses(exp, number_of_oprand))
            else:
                expression = " ".join(exp)
            
            #判斷是否有重復
            if self.is_repeat(exp_list, expression) or suffix_to_value(to_suffix(expression)) == False:
                continue
            else:
                exp_list.append(expression)
                i = i + 1

(2)插入括號代碼邏輯

如果生成的括號表達式形如 (1 + 2/3 + 3),則認為是沒有意義的括號,需要重新插入。

if exp:
            exp_length = len(exp)
            left_position = randint(0,int(num/2))
            right_position = randint(left_position+1, int(num/2)+1)
            mark = -1
            for i in range(exp_length):
                if exp[i] in ['+', '-', 'x', '÷']:
                    expression.append(exp[i])
                else:
                    mark += 1
                    if mark == left_position:
                        expression.append('(')
                        expression.append(exp[i])
                    elif mark == right_position:
                        expression.append(exp[i])
                        expression.append(')')
                    else:
                        expression.append(exp[i])
        #如果生成的括號表達式形如 (1 + 2/3 + 3) 則重新生成
        if expression[0] == '(' and expression[-1] ==')':
            expression = self.generate_parentheses(exp, number_of_oprand)
            return expression

(3)中綴轉后綴和求值

中綴表達式轉后綴表達式的邏輯:

  1. 初始化兩個棧,分為運算符棧和后綴表達式棧,遍歷表達式列表,如果遇到運算符:

    a. 如果運算符棧為空,則直接入棧

    b. 如果運算符棧不為空,則取出棧頂top元素

    • 如果棧頂top元素是左括號或者算術優先級高於棧頂top元素,那么就直接入棧

    • 否則就入棧后綴表達式棧

  2. 如果遇到左括號:

    • 左括號直接入運算符棧

  3. 如果遇到右括號:

    • 如果運算符棧不為空,那么直接出棧,添加到后綴表達式棧,直到遇到左括號

  4. 遇到運算數直接入后綴表達式棧

suffix_stack = []  #后綴表達式結果
    ops_stack = []  #操作符棧
    infix = exp.split(' ')
    #print(infix)
    for item in infix:
        if item in ['+', '-', 'x', '÷']: #遇到運算符
            while len(ops_stack) >= 0:
                if len(ops_stack) == 0:
                    ops_stack.append(item)
                    break
                op = ops_stack.pop()
                if op == '(' or ops_rule[item] > ops_rule[op]:
                    ops_stack.append(op)
                    ops_stack.append(item)
                    break
                else:
                    suffix_stack.append(op)
        elif item == '(': # 左括號直接入棧
            ops_stack.append(item)
        elif item == ')': #右括號
            while len(ops_stack) > 0:
                op = ops_stack.pop()
                if op == "(":
                    break
                else:
                    suffix_stack.append(op)
        else:
            suffix_stack.append(item) # 數值直接入棧
    
    while len(ops_stack) > 0:
        suffix_stack.append(ops_stack.pop())

四. 運行測試

文件說明:

文件 說明
Answer.txt 生成表達式答案文件
Exercises.txt 生成表達式存儲的文件
Grade.txt 題目對錯數量統計文件

 

結果:

效能分析:

  • 因為涉及到二叉樹遞歸等操作,所以會有很多時間和空間的開銷
  • IO讀寫也影響運算的時間

五. 項目總結

這次結對編程中,我和林賢傑一起深入分析項目的需求分析,找到實現需求的具體思路,設計具體實現的過程,我負責編碼,林賢傑同學在旁邊觀察協助。在此過程中,我們也遇到了一些問題,也找到了解決的思路。總之,在結對編程中有很大的收獲,實現1+1 > 2 。


免責聲明!

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



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