自動生成小學四則運算題目

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後稍微修改一下
  • 因爲數據結構定義比較混亂,可拓展性有待考量。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章