GitHub倉庫:https://github.com/15crmor/Arithmeticgit
項目要求:github
題目:實現一個自動生成小學四則運算題目的命令行程序說明:編程
說明:app
天然數:0, 1, 2, …。dom
e = n | e1 + e2 | e1 − e2 | e1 × e2 | e1 ÷ e2 | (e),函數
其中e, e1和e2爲表達式,n爲天然數或真分數。學習
需求:測試
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屬性中:
1. 2.
3.
4.
負數與除數爲0:
負數的產生是因爲t1 < t2,同時若在進行減法後的值爲0且作除數,則出現除數爲0狀況,因此經過條件判斷捨棄在運算符爲「-」時,t1 <= t2 的二叉樹
判斷表達式是否重複
轉換成規範二叉樹,而後再以逆波蘭格式轉成字符串形式,存在一個列表裏,每次生成了一個新式子後,就按上面方法生成規範化的二叉樹並轉成逆波蘭形式字符串與列表中全部元素比較。
其返回的字符串逆波蘭式相同,即2*6+5*7與7*5+6*2是同樣的表達式。
主要的類和函數有:
class Arith(object)
class BinaryTree(object) # 二叉樹
class Caculation(object)
class Check(object) # 判重
class Compare(object)
class Create(object) # 生成四則運算表達式
class Fractions(object) # 分數類
class CreateTree(object) # 生成規範二叉樹
代碼說明:
生成四則運算表達式:
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道題的題目與答案
文件答案比較
生成一萬道題目
如下爲一萬到題目和答案的連接
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 |
項目小結:
在本次結點編程四則運算項目中,與楊浩政同窗一塊兒組隊討論寫代碼。首先呢,咱們一塊兒討論如何實現這個項目,共同討論書寫設計文檔。在寫設計文檔的過程當中,咱們不少想法都不同,因爲個人編程水平比較低,缺少經驗,通常來講,都是以楊浩政同窗的想法爲主,個人想法爲輔。在交流設計思路的過程當中,咱們兩人的思惟也會碰撞出更好的解決思路。在雙方都認爲本身的想法比較好時,會各自說出本身想法的優勢,而後再共同思考哪一個實現途徑更優。最大的感觸是:多聽聽不一樣的想法,會使本身的思惟更開闊。(來自張兆敏同窗的小結)
本次結對編程我主要負責代碼實現,此次編程對個人幫助很大,讓我明白了只有思路足夠清晰才能更快更好地實現功能,在拿到項目後必定要多思考如何實現更簡潔方便,而不是邊寫邊想,到最後不停修改。與隊友的討論也啓發了我一些思路,只要思路上能找到合適的方法,那麼實現起來就會容易快速不少。(來自楊浩政的小結)