項目成員:梁華超、林賢傑
項目倉庫: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)中綴轉后綴和求值
-
初始化兩個棧,分為運算符棧和后綴表達式棧,遍歷表達式列表,如果遇到運算符:
a. 如果運算符棧為空,則直接入棧
b. 如果運算符棧不為空,則取出棧頂top元素
-
如果棧頂top元素是左括號或者算術優先級高於棧頂top元素,那么就直接入棧
-
-
-
如果遇到左括號:
-
左括號直接入運算符棧
-
-
如果遇到右括號:
-
如果運算符棧不為空,那么直接出棧,添加到后綴表達式棧,直到遇到左括號
-
-
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讀寫也影響運算的時間
五. 項目總結