四則運算 Python實現(楊浩政,張兆敏)

四則運算

GitHub倉庫:https://github.com/15crmor/Arithmeticgit

 

項目要求:github

題目:實現一個自動生成小學四則運算題目的命令行程序說明:編程

說明:app

天然數:0, 1, 2, …。dom

  • 真分數:1/2, 1/3, 2/3, 1/4, 1’1/2, …。
  • 運算符:+, −, ×, ÷。
  • 括號:(, )。
  • 等號:=。
  • 分隔符:空格(用於四則運算符和等號先後)。
  • 算術表達式:

e = n | e1 + e2 | e1 − e2 | e1 × e2 | e1 ÷ e2 | (e),函數

其中e, e1和e2爲表達式,n爲天然數或真分數。學習

  • 四則運算題目:e = ,其中e爲算術表達式。

  需求:測試

1.(完成)使用 -n 參數控制生成題目的個數編碼

2.(完成)使用 -r 參數控制題目中數值(天然數、真分數和真分數分母)的範圍,該參數能夠設置爲1或其餘天然數。spa

3.(完成)生成的題目中計算過程不能產生負數

4.(完成)生成的題目中若是存在形如e1 ÷ e2的子表達式,那麼其結果應是真分數

5.(完成)每道題目中出現的運算符個數不超過3個。

6.(完成)程序一次運行生成的題目不能重複,即任何兩道題目不能經過有限次交換+×左右的算術表達式變換爲同一道題目。生成的題目存入執行程序的當前目錄下的Exercises.txt文件

7.(完成)在生成題目的同時,計算出全部題目的答案,並存入執行程序的當前目錄下的Answers.txt文件

8.(完成)程序應能支持一萬道題目的生成。

9.(完成)程序支持對給定的題目文件和答案文件,斷定答案中的對錯並進行數量統計。統計結果輸出到文件Grade.txt,格式以下: 

Correct: 5 (1, 3, 5, 7, 9)

Wrong: 5 (2, 4, 6, 8, 10)

 

 

設計實現過程

 

整體構思:

 

 

 

 

 

 

表達式:

  將整數也看做分數來生成隨機數,爲Fraction類型,有numerator,denominator兩個屬性,而後根據運算符個數生成表達式,如:A+B*C/D。

  再轉換成逆波蘭表達式,而後轉化成規範化的二叉樹如:隨機生成表達式:2*6+5*7 à  逆波蘭表達式:26*57*+ , 再轉化成二叉樹的同時進行規範化並計算結果存入樹的value屬性中:

 

  •  二叉樹的葉子節點都爲數字,非葉子節點都爲運算符,當規範化二叉樹時,實際上是遍歷逆波蘭式列表中的元素,而後建立一個空樹,若遍歷到的元素不是運算符,就將數字添加入樹的屬性,並將樹存入列表stack中,如果運算符,則彈出列表中後兩個樹t二、t1,此時兩個樹中狀況無非是

1.                                                                                                                                           2.                                                                                                                           

                                                                                                    

 

3.

 

       

 

 

 

4. 

 

 

  •    經過計算比較value值(有運算符則爲兩數運算後的值,不管是什麼運算符,都爲t1 op t2,並不是左子樹 op 右子樹)將vlaue較大的設爲左子樹並計算新的value,若value值相等,則經過斷定優先級來肯定左右子樹。

 

負數與除數爲0:

  負數的產生是因爲t1 < t2,同時若在進行減法後的值爲0且作除數,則出現除數爲0狀況,因此經過條件判斷捨棄在運算符爲「-」時,t1 <= t2 的二叉樹

 

判斷表達式是否重複

轉換成規範二叉樹,而後再以逆波蘭格式轉成字符串形式,存在一個列表裏,每次生成了一個新式子後,就按上面方法生成規範化的二叉樹並轉成逆波蘭形式字符串與列表中全部元素比較。

  •   例如2*6+5*7與7*5+6*2生成的規範化二叉樹都爲:

                 

 

 

其返回的字符串逆波蘭式相同,即2*6+5*77*5+6*2是同樣的表達式。

 

主要的類和函數有:

  • class Arith(object)
    •  creat(self, problem_number, r) #  生成四則運算和答案在判重後寫入文件
    •    main(self, arith, argv) #  支持命令行輸入參數
  • class BinaryTree(object) # 二叉樹
    •  out_put_tree(self, tree, s)  #  返回二叉樹逆波蘭形式的字符串
  • class Caculation(object) 
    •  caulate(self, op, f1, f2)  #  計算f1 op f2 的值(op爲運算符)
    •    max(self, num1, num2)  #  比較兩分數大小
  • class Check(object) # 判重
    •  check_tree(self, tree)  #  對二叉樹進行判重,若不重複返回True
  • class Compare(object) 
    •  grade(self, exercise_file, answer_file)  #  比較兩文件答案並記錄
  • class Create(object) # 生成四則運算表達式
    •    create_operator(self)  #  隨機生成運算符 
    •    create_arith(self, r)  #  生成範圍在r之內的四則運算表達式
    •    proper_fraction(self, list)  #  將假分數化爲帶分數
  • class Fractions(object) # 分數類
  • class CreateTree(object) # 生成規範二叉樹
    •  toRPN(self, list)  #  生成逆波蘭表達式
    •    createTree(self, suffix)  #  將逆波蘭式轉化爲規範化二叉樹
    •    priority(self, operatorout, operatorin)  #  斷定優先級

 

代碼說明:

 生成四則運算表達式:

    def create_arith(self, r):
        x = 0
        list = []
        operator_num = random.randint(1, 3)
        e1 = Create()
        e2 = Create()
        if operator_num == 1:
            list.append(e1.create_number(r))
            list.append(e2.create_operator())
            list.append(e1.create_number(r))
        elif operator_num == 2:
            start = random.randint(0, 2)
            end = 0
            if start > 0:
                end = start + 1
            for i in range(1, 4):
                if i == start:
                    list.append("(")
                list.append(e1.create_number(r))
                if i == end:
                    list.append(")")
                list.append(e2.create_operator())
            list.pop()
        elif operator_num == 3:
            start = random.randint(0, 3)
            end = 0
            if start > 0:
                end = start + 1 + random.randint(0, 1)
                if end >= 4:
                    end = 4
            for i in range(1, 5):
                if i == start:
                    list.append("(")
                list.append(e1.create_number(r))
                if i == end:
                    list.append(")")
                list.append(e2.create_operator())
            list.pop()
        else:
            list.append(e1.create_number(r))
            list.append(e2.create_operator())
            list.append(e1.create_number(r))
        return list

假分數化爲帶分數:

     # 將表達式假分數轉化爲帶分數
    def proper_fraction(self, list):
        num = 0
        for fract in list:
            if type(fract) == Fraction:
                n1 = fract.numerator
                n2 = fract.denominator
                if n2 == 1:
                    num += 1
                    continue
                elif n1 > n2:
                    sub = int(n1/n2)
                    n1 = n1 % n2
                    list[num] = '%d%s%d/%d' %(sub, '', n1,n2)
            num += 1
        return list

    # 將答案假分數轉化爲帶分數
    def pop_fracte(self, re):
        n1 = re.numerator
        n2 = re.denominator
        if n2 == 1:
            return n1
        elif n1 < n2:
            return re
        else:
            sub = int(n1/n2)
            n1 = n1 % n2
            return '%d%s%d/%d' % (sub, '', n1, n2)

生成逆波蘭式:

    def toRPN(self, list):
        right = []
        aStack = []
        position = 0
        while True:
            if self.isOperator(list[position]):
                if list == [] or list[position] == "(":
                    aStack.append(list[position])
                else:
                    if list[position] == ")":
                        while True:
                            if aStack != [] and aStack[-1] != "(":
                                operator = aStack.pop()
                                right.append(operator)
                            else:
                                if aStack != []:
                                    aStack.pop()
                                break
                    else:
                        while True:
                            if aStack != [] and self.priority(list[position], aStack[-1]):
                                operator = aStack.pop()
                                if operator != "(":
                                    right.append(operator)
                            else:
                                break
                        aStack.append(list[position])
            else:
                right.append(list[position])
            position = position + 1
            if position >= len(list):
                break
        while aStack != []:
            operator = aStack.pop()
            if operator != "(":
                right.append(operator)
        return right

將逆波蘭式轉化成規範化的二叉樹:

   def createTree(self, suffix):
        stacks = []

        for i in range(0, len(suffix)):
            tree = BinaryTree()
            ob = suffix[i]
            c = Caculation.Caculation()
            if self.isOperator(ob):
                t2 = BinaryTree()
                t1 = BinaryTree()
                t2 = stacks.pop()
                t1 = stacks.pop()
                if ob == '-' and t1.value <= t2.value:
                    return None
                else:
                    if self.maxTree(t1, t2):
                        tree.set_date(ob)
                        tree.set_left(t1)
                        tree.set_right(t2)
                        tree.set_value(c.caulate(ob, t1.value, t2.value))
                    else:
                        tree.set_date(ob)
                        tree.set_left(t2)
                        tree.set_right(t1)
                        tree.set_value(c.caulate(ob, t1.value, t2.value))
                    stacks.append(tree)
            else:
                tree.set_value(ob)
                tree.set_date(ob)
                stacks.append(tree)
        return tree

 對二叉樹判重:

    #  對二叉樹進行判重
    def check_tree(self, tree):
        if self.check == []:
            self.check.append(tree)
            return True
        else:
            for i in range(len(self.check)):
                if self.check[i] == tree:
                    return False
        self.check.append(tree)
        return True

二叉樹類:

 

class BinaryTree(object):
    def __init__(self):
        self.date = None
        self.left = None
        self.right = None
        self.value = None

    def tree(self, date, left, right, value):
        self.date = date
        self.left = left
        self.right = right
        self.value = value

    def set_date(self, date):
        self.date = date

    def set_left(self, left):
        self.left = left

    def set_right(self, right):
        self.right = right

    def set_value(self, value):
        self.value = value

    def to_string(self, tree):
        s = ""
        s = self.out_put_tree(tree, s)
        return s

    def out_put_tree(self, tree, s):
        if tree != None:
            s1 = self.out_put_tree(tree.left, s)
            s2 = self.out_put_tree(tree.right, s)
            if type(tree.date) == Fractions.Fractions:
                return str(s1) + str(s2) + str(tree.date.to_string())
            else:
                return str(s1) + str(s2) + str(tree.date)
        return s

 

分數類:

class Fractions(object):
    def __init__(self):
        self.numerator = None
        self.denominator = None

    def setNumerator(self,numerator):
        self.numerator = numerator

    def setDenominator(self,denominator):
        self.denominator = denominator

    def toString(self):
        a = self.numerator
        b = self.denominator
        return str(Fraction(a, b))

計算參數值:

    def caulate(self, op, f1, f2):
        result = Fractions.Fractions()
        n1 = int(f1.numerator)
        d1 = int(f1.denominator)
        n2 = int(f2.numerator)
        d2 = int(f2.denominator)
        list = []
        if op == '+':
            re = Fraction(n1, d1) + Fraction(n2, d2)

        elif op == '-':
            re = Fraction(n1, d1) - Fraction(n2, d2)

        elif op == '×':
            re = Fraction(n1, d1) * Fraction(n2, d2)

        else:
            re = Fraction(n1, d1) / Fraction(n2, d2)

        return re

文件比較,判斷錯誤答案並記錄:

 #  對兩個文件中的答案進行比較並記錄
    def grade(self, exercise_file, answer_file):
        correct = []
        wrong = []
        co = 0
        wr = 0
        with open(answer_file, 'r') as f1, open(exercise_file, 'r', encoding='utf-8') as f2:
            answers = f2.readlines()
            line = 0
            for r_answers in f1.readlines():
                if answers[line] == r_answers:
                    co += 1
                    correct.append(line+1)
                else:
                    wr += 1
                    wrong.append(line+1)
                line += 1
        with open('gread.txt', 'w') as f3:
            f3.write(f"Correct: {str(co)} ({', '.join(str(s) for s in correct if s not in [None])})" + '\n')
            f3.write(f"Correct: {str(wr)} ({', '.join(str(s) for s in wrong if s not in [None])})" + '\n')
        print("文件比較完成")

生成表達式判重後寫入文件:

    # 生成問題和答案在判重後寫入文件
    def creat(self, problem_number, r):
        creat_pro = CreatProblem.Create()
        t = BinaryTree.BinaryTree()
        c = Check.Check()
        with open("Exercises.txt", "w") as file1, open("Answer.txt", "w") as file2:
            num = 0
            while num < problem_number:
                arith = creat_pro.create_arith(r)  # 生成四則運算列表
                Ju = CreateTree.Judge()
                al = Ju.toRPN(arith)  # 將列表轉換成逆波蘭式
                print(al)
                string = creat_pro.to_string(creat_pro.proper_fraction(arith))
                ta = Ju.createTree(al)  # 將逆波蘭式生成規範二叉樹
                print(t.to_string(ta))
                if ta:
                    val = str(creat_pro.pop_fracte(ta.value))
                    if c.check_tree(t.to_string(ta)):  # 進行判重
                        file1.write("%d. " % (num+1) + string + '\n')
                        file2.write("%d. " % (num+1) + val + '\n')
                        num +=1
        print("四則運算題目生成完畢,數量爲%d個" % problem_number)

命令行程序入口:

    # 支持命令行鍵入參數
    def main(self, arith, argv):
        problem_number = None
        num_range = None
        exercise_file = None
        answer_file = None
        try:
            opts, args = getopt.getopt(argv, "n:r:e:a:")
        except getopt.GetoptError:
            print('Error: arith.py -n <problem_number> -r <num_range>')
            print('   or: test_arg.py -e -e <exercisefile>.txt -a <answerfile>.txt')
            sys.exit(2)

        for opt, arg in opts:
            if opt in("-n"):
                problem_number = int(arg)
            elif opt in ("-r"):
                num_range = int(arg)
            elif opt in("-e"):
                exercise_file = arg
            elif opt in("-a"):
                answer_file = arg
        if problem_number and num_range:
            arith.creat(problem_number, num_range)
        elif exercise_file and answer_file:
            compare = Compare.Compare()
            compare.grade('ReAnswer.txt', 'Answer.txt')
        else:
            print('Error: arith.py -n <problem_number> -r <num_range>')
            print('   or: test_arg.py -e -e <exercisefile>.txt -a <answerfile>.txt')

 

測試運行

參數錯誤提示

 

生成10道題的題目與答案

 

文件答案比較

 

 

生成一萬道題目

如下爲一萬到題目和答案的連接

點此查看一萬條表達式

 點此查看錶達式答案

 PSP

PSP2.1

Personal Software Process Stages

預估耗時(分鐘)

實際耗時(分鐘)

Planning

計劃

 60

 80

· Estimate

· 估計這個任務須要多少時間

 50

 50

Development

開發

 900

800

· Analysis

· 需求分析 (包括學習新技術)

 40

 80

· Design Spec

· 生成設計文檔

 60

 60

· Design Review

· 設計複審 (和同事審覈設計文檔)

 20

 30

· Coding Standard

· 代碼規範 (爲目前的開發制定合適的規範)

 30

 30

· Design

· 具體設計

 60

 180

· Coding

· 具體編碼

 500

 800

· Code Review

· 代碼複審

 100

 120

· Test

· 測試(自我測試,修改代碼,提交修改)

 120

150

Reporting

報告

 60

 90

· Test Report

· 測試報告

 30

 30

· Size Measurement

· 計算工做量

 20

 30

· Postmortem & Process Improvement Plan

· 過後總結, 並提出過程改進計劃

 20

 20

合計

 

 2070

 2550

 

 

項目小結:

      在本次結點編程四則運算項目中,與楊浩政同窗一塊兒組隊討論寫代碼。首先呢,咱們一塊兒討論如何實現這個項目,共同討論書寫設計文檔。在寫設計文檔的過程當中,咱們不少想法都不同,因爲個人編程水平比較低,缺少經驗,通常來講,都是以楊浩政同窗的想法爲主,個人想法爲輔。在交流設計思路的過程當中,咱們兩人的思惟也會碰撞出更好的解決思路。在雙方都認爲本身的想法比較好時,會各自說出本身想法的優勢,而後再共同思考哪一個實現途徑更優。最大的感觸是:多聽聽不一樣的想法,會使本身的思惟更開闊。(來自張兆敏同窗的小結)

  本次結對編程我主要負責代碼實現,此次編程對個人幫助很大,讓我明白了只有思路足夠清晰才能更快更好地實現功能,在拿到項目後必定要多思考如何實現更簡潔方便,而不是邊寫邊想,到最後不停修改。與隊友的討論也啓發了我一些思路,只要思路上能找到合適的方法,那麼實現起來就會容易快速不少。(來自楊浩政的小結)

相關文章
相關標籤/搜索