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後稍微修改一下
- 因爲數據結構定義比較混亂,可拓展性有待考量。