Github項目地址:https://github.com/yyffish/ArithmeticGenerators
結對項目組成員:18軟4庾藝鋒(3118005117)、18軟4王澤鑫(3118005107)
項目相關要求
-
- 使用 -n 參數控制生成題目的個數
- 使用 -r 參數控制題目中數值(自然數、真分數和真分數分母)的范圍
- 生成的題目中計算過程不能產生負數,也就是說算術表達式中如果存在形如e1− e2的子表達式,那么e1≥ e2
- 生成的題目中如果存在形如e1÷ e2的子表達式,那么其結果應是真分數
- 每道題目中出現的運算符個數不超過3個
- 程序一次運行生成的題目不能重復,即任何兩道題目不能通過有限次交換+和×左右的算術表達式變換為同一道題目
- 在生成題目的同時,計算出所有題目的答案,並存入執行程序的當前目錄下的Answers.txt文件
- 程序應能支持一萬道題目的生成
- 程序支持對給定的題目文件和答案文件,判定答案中的對錯並進行數量統計
困難及解決方法
使用 -n 參數控制生成題目的個數,使用 -r 參數控制題目中數值(自然數、真分數和真分數分母)的范圍
-
- 用戶在前端輸入題目數量和數值范圍
- 提交表單到servlet進行處理
- 返回生成的算式到前端
生成的題目中計算過程不能產生負數,也就是說算術表達式中如果存在形如e1− e2的子表達式,那么e1≥ e2
-
- 在進行減法運算時,若e1<e2,返回-1
- 在添加到List前進行判斷,若算式結果為負數,則不添加到List中
生成的題目中如果存在形如e1÷ e2的子表達式,那么其結果應是真分數
-
- 在進行除法運算時,若e1>e2,返回-1
- 在添加到List前進行判斷,若算式結果為負數,則不添加到List中
每道題目中出現的運算符個數不超過3個
-
- 由此可以得知運算符數量為1~3個
- 參數為2~4個
- 用隨機數判斷運算符數量和參數數量
程序一次運行生成的題目不能重復,即任何兩道題目不能通過有限次交換+和×左右的算術表達式變換為同一道題目
-
- 用判重函數判斷表達式列表中是否有重復項
在生成題目的同時,計算出所有題目的答案,並存入執行程序的當前目錄下的Answers.txt文件
-
- 將含有括號的表達式轉為逆波蘭表達式
- 將逆波蘭表達式入棧計算結果
- 在添加到List前判重和判負
- 將答案List通過IO流寫入到txt文件中
程序應能支持一萬道題目的生成
-
- 生成1w到題目測試沒問題
程序支持對給定的題目文件和答案文件,判定答案中的對錯並進行數量統計
-
- 用戶可以點擊檢查答案
- 默認檢查桌面的Answers.txt和Exercises.txt
- 對比答案並生成餅狀圖
項目結構
關鍵代碼or設計說明
通過IO流寫入到txt文件中
1 package com.AG.dao; 2 3 import java.io.BufferedReader; 4 import java.io.BufferedWriter; 5 import java.io.File; 6 import java.io.FileReader; 7 import java.io.FileWriter; 8 import java.io.IOException; 9 import java.util.ArrayList; 10 import java.util.List; 11 12 public class FileUtils { 13 14 /** 15 * 創建一個txt文件 16 * 17 * @param filePath 文件路徑 18 * @return 創建成功返回true,已存在返回false 19 */ 20 public boolean creatFile(String filePath) { 21 boolean flag = false; 22 File file = new File(filePath); 23 if (file.exists()) { 24 try { 25 file.createNewFile(); 26 } catch (IOException e) { 27 // TODO: handle exception 28 e.printStackTrace(); 29 } 30 flag = true; 31 } 32 return flag; 33 } 34 35 /** 36 * 讀取題目文件或答案文件 都是一行一行讀取 37 * 38 * @param filePath 文件路徑 39 * @return 返回題目或答案List 40 */ 41 public List<String> readTxtFile(String filePath) { 42 List<String> list = new ArrayList<String>(); 43 String thisLine = null; 44 File file = new File(filePath); 45 if (file.exists() && file.isFile()) { 46 try { 47 BufferedReader bufferedReader = new BufferedReader(new FileReader(file)); 48 while ((thisLine = bufferedReader.readLine()) != null) { 49 list.add(thisLine); 50 } 51 bufferedReader.close(); 52 } catch (Exception e) { 53 // TODO: handle exception 54 e.printStackTrace(); 55 } 56 } 57 return list; 58 } 59 60 /** 61 * 讀txt文件 62 * 63 * @param list 64 * @param filePath 65 * @return 66 */ 67 public boolean writeTxtFile(List<String> list, String filePath) { 68 boolean flag = false; 69 File file = new File(filePath); 70 try { 71 if (!file.exists()) { 72 file.createNewFile(); 73 } 74 BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath)); 75 for (String string : list) { 76 bufferedWriter.write(string); 77 bufferedWriter.newLine(); 78 } 79 bufferedWriter.close(); 80 } catch (IOException e) { 81 // TODO: handle exception 82 e.printStackTrace(); 83 } 84 flag = true; 85 return flag; 86 } 87 88 }
通過逆波蘭表達式計算結果
- 如有表達式 1 2 3 + +
- 1 2 3 依次入棧
- 遇到 + 號,2 3 出棧 進行加法運算 結果為 5
- 5入棧
- 遇到 + 號, 5 1出棧 再進行加法運算 結果為 6
- 棧空 結果為6
1 /** 2 * 通過逆波蘭表達式計算結果 3 * 4 * @param 表達式 5 * @return Stringl類型的計算結果 6 */ 7 private String calcRevPolishNotation(String express) { 8 Stack<String> stack = new Stack<>(); 9 String[] expressArr = express.split(" "); 10 for (int i = 0; i < expressArr.length; i++) { 11 if (expressArr[i].matches("[0-9]+/[0-9]+")) { 12 stack.push(expressArr[i]); 13 // + - * / 運算符的處理 14 } else if (expressArr[i].matches("[\\+\\-\\*\\÷]")) { 15 String k1 = stack.pop(); 16 String k2 = stack.pop(); 17 // 計算結果 18 String res = calcValue(k1, k2, expressArr[i]); 19 stack.push(res); 20 } 21 22 } 23 return stack.pop(); 24 }
將中綴表達式轉換為后綴表達式(逆波蘭表達式)
- 就是通過棧來講公式轉為逆波蘭表達式 如 1 + ( 2 + 3 ) => 1 2 3 + +
- 數字直接入棧
- 如果是運算符 則將棧中的兩個數字出棧進行運算
- 若是括號 我還沒理解
1 /** 2 * 將中綴表達式轉換為后綴表達式(逆波蘭表達式) 3 * 4 * @param express 5 * @return 6 */ 7 private String transfer(String express) { 8 Stack<String> stack = new Stack<>(); 9 List<String> list = new ArrayList<>(); 10 String[] expressArr = express.split(" "); 11 for (int i = 0; i < expressArr.length; i++) { 12 if (expressArr[i].matches("[0-9]+/[0-9]+")) { 13 list.add(expressArr[i]); 14 } else if (expressArr[i].matches("[\\+\\-\\*\\÷]")) { 15 // 如果stack為空 16 if (stack.isEmpty()) { 17 stack.push(expressArr[i]); 18 continue; 19 } 20 // 不為空 21 22 // 上一個元素不為(,且當前運算符優先級小於上一個元素則,將比這個運算符優先級大的元素全部加入到隊列中 23 while (!stack.isEmpty() && !stack.lastElement().equals("(") 24 && !comparePriority(expressArr[i], stack.lastElement())) { 25 list.add(stack.pop()); 26 } 27 stack.push(expressArr[i]); 28 } else if ("(".equals(expressArr[i])) { 29 // 遇到左小括號無條件加入 30 stack.push(expressArr[i]); 31 } else if (")".equals(expressArr[i])) { 32 // 遇到右小括號,則尋找上一堆小括號,然后把中間的值全部放入隊列中 33 while (!("(").equals(stack.lastElement())) { 34 list.add(stack.pop()); 35 } 36 // 上述循環停止,這棧頂元素必為"(" 37 stack.pop(); 38 } 39 } 40 // 將棧中剩余元素加入到隊列中 41 while (!stack.isEmpty()) { 42 list.add(stack.pop()); 43 } 44 StringBuffer stringBuffer = new StringBuffer(); 45 // 變成字符串 46 for (String s : list) { 47 stringBuffer.append(s + " "); 48 } 49 return stringBuffer.toString(); 50 }
輾轉相除法求公約數
1 /** 2 * 輾轉相除法求公約數 3 * 4 * @param numerator 分子 5 * @param denominator 分母 6 * @return 最大公約數 7 */ 8 public Integer gcd(Integer numerator, Integer denominator) { 9 int temp; 10 while (denominator > 0) { 11 temp = numerator % denominator; 12 numerator = denominator; 13 denominator = temp; 14 } 15 return numerator; 16 }
比較兩條表示式是否相同
- 通過Set的不可重復來校驗參數列表ParameterList和運算符列表OperatorList是否重復
- 因為想不出可以區別兩個表達式的算法,我們就折衷了一下,只要判斷兩個表達式的操作數還有運算符完全相同,則判定兩個表達式相同。
- 這種判斷方式是有問題的,會把一些不是相同的表達式誤判為相同,黔驢技窮之下,顧不了這么多了,能滿足需求就行了,以后有新的想法再進行優化。
1 /** 2 * 比較express表達式是否存在於examList表達式列表中 3 * 4 * @param examList 表達式列表 5 * @param express 表達式 6 * @return 結果 存在返回true 不存在返回false 7 */ 8 public Boolean isExistList(List<Expression> examList, Expression express) { 9 int len = examList.size(); 10 for (int i = 0; i < len; i++) { 11 if (isSame(express, examList.get(i))) 12 return true; 13 } 14 return false; 15 } 16 17 /** 18 * 比較兩條表示式是否相同 19 * 20 * @param express1 表達式1 21 * @param express2 表達式2 22 * @return 相同返回true,否則返回false 23 */ 24 private Boolean isSame(Expression express1, Expression express2) { 25 // 運算符數目不相等,直接返回 26 if (express1.getOperatorList().size() != express2.getOperatorList().size()) 27 return false; 28 Set<Map<Integer, Integer>> parameterSet = new HashSet<Map<Integer, Integer>>(); 29 Set<String> operatorSet = new HashSet<String>(); 30 List<Map<Integer, Integer>> parameterList = express1.getParameterList();// 獲取表達式1的參數列表 31 List<String> operatorList = express1.getOperatorList();// 獲取表達式1的操作符列表 32 int len = operatorList.size(); 33 // 將參數和操作符列表分別加進Set 34 for (int i = 0; i < len; i++) { 35 operatorSet.add(operatorList.get(i)); 36 parameterSet.add(parameterList.get(i)); 37 } 38 parameterSet.add(parameterList.get(len)); 39 parameterList = express2.getParameterList();// 獲取表達式2的參數列表 40 operatorList = express2.getOperatorList();// 獲取表達式2的操作符列表 41 for (int i = 0; i < len; i++) { 42 if (!operatorSet.contains(operatorList.get(i)) || !parameterSet.contains(parameterList.get(i))) { 43 return false; 44 } 45 } 46 if (!parameterSet.contains(parameterList.get(len))) 47 return false; 48 return true; 49 }
運行測試
向服務器發送請求 http://localhost:8080/ArithmeticGenerators/tableForm.jsp
用戶輸入題目數量和最大值,提交form表達,發送exam請求
生成從1~10的題目
隨意修改幾個答案后進行答案校驗
PSP
因為項目用時較長,以下時間都是預估的
PSP2.1 | Personal Software Process Stages | 預估耗時(分鍾) | 實際耗時(分鍾) |
---|---|---|---|
Planning | 計划 | 90 | 90 |
· Estimate | · 估計這個任務需要多少時間 | 90 | 90 |
Development | 開發 | 2140 | 2140 |
· Analysis | · 需求分析 (包括學習新技術) | 30 | 30 |
· Design Spec | · 生成設計文檔 | 60 | 60 |
· Design Review | · 設計復審 (和同事審核設計文檔) | 20 | 20 |
· Coding Standard | · 代碼規范 (為目前的開發制定合適的規范) | 30 | 30 |
· Design | · 具體設計 | 120 | 120 |
· Coding | · 具體編碼 | 2000 | 2000 |
· Code Review | · 代碼復審 | 30 | 30 |
· Test | · 測試(自我測試,修改代碼,提交修改) | 50 | 50 |
Reporting | 報告 | 110 | 110 |
· Test Report | · 測試報告 | 60 | 60 |
· Size Measurement | · 計算工作量 | 20 | 20 |
· Postmortem & Process Improvement Plan | · 事后總結, 並提出過程改進計划 | 30 | 30 |
合計 | 2540 | 2540 |
項目總結
- 作為結對項目,最重要的是合作。我認為這次不足的就是隊友之間的相互配合了。雖然有疫情的原因,不能夠面對面交流,經常出現隊友不能理解我的思路而不能編寫出合適的代碼。在這方面還是需要多項有經驗的同學請教。
- 數據結構定義比較混亂,應該少使用Map和List,應盡量定義為數組
- 本題的唯一算法就是逆波蘭表達式的轉換,盡管研究了很久,但還是看不懂,只能copy后稍微修改一下
- 因為數據結構定義比較混亂,可拓展性有待考量。