結對項目:四則運算題目生成器(Java)


項目Github地址ExercisesGenerator

一、需求分析

需求描述 是否實現
控制生成題目的個數
控制題目中數值范圍
計算過程不能產生負數,除法的結果必須是真分數,題目不能重復,運算符不能超過3個
生成的題目存入執行程序的當前目錄下的Exercises.txt文件
題目的答案存入執行程序的當前目錄下的Answers.txt文件
能支持一萬道題目的生成
支持對給定的題目文件和答案文件,判定答案中的對錯並進行數量統計
統計結果輸出到文件Grade.txt
具有圖形化的操作界面

二、開發計划

功能 描述 進度
生成題目 隨機生成操作數和運算符,組成有效的四則運算表達式 完成
計算結果 根據生成的表達式,計算生成正確的結果 完成
批卷 根據指定的題目文件和答案文件,輸出成績結果 完成
UI界面設計 設計軟件的GUI界面 完成
UI界面實現 用Javafx實現GUI界面 完成
功能測試與故障修復 測試程序的功能,修復出現的故障 完成
性能分析與優化 分析程序執行的性能,優化性能表現 完成

三、實現方案

3.1 項目結構

Package generator

結構說明

上圖展示了程序中核心的類和方法,其中GenerateController負責生成題目的功能,CheckController負責批卷功能,兩者都依賴於底層的GenerateService,而GenerateService中進行四則運算的功能依賴於OperationService

3.2 代碼說明

3.2.1 出題功能代碼

    /**
     * 生成指定數目包含答案的有效題目
     */
    @Override
    public void generateExercises(int exercisesNum, int numRange) throws IOException {
        int count = 0;
        while (count < exercisesNum) {
            Exercises exercises = generateQuestion(numRange);
            generateAnswer(exercises);
            //有效題目加入隊列
            if (validate(exercises, exercisesSet)) {
                //生成可以輸出的題目樣式
                exercisesQueue.add(exercises);
                count++;
                //放入查重集合
                exercisesSet.add(exercises.getSimplestFormatQuestion());                exercisesSet.add(CalculateUtil.getEqualsExpression(exercises.getSimplestFormatQuestion()));
            }
        }

    }

    /**
     * 生成運算式
     */
    private Exercises generateQuestion(int numRange) {
        Exercises e = new Exercises();
        int signalNum = CalculateUtil.getRandomNum(1, 3);
        e.setOperatorNumber(signalNum);
        for (int i = 0; i < e.getOperatorNumber(); i++) {
            //添加運算符
            e.addValue(i, generateOperator());

        }
        for (int i = e.getOperatorNumber(); i < 2 * e.getOperatorNumber() + 1; i++) {
            //添加運算數
            e.addValue(i, generateNum(numRange));
        }
        return e;
    }

實現思路

  1. 隨機生成運算符個數
  2. 根據運算符個數隨機生成運算符
  3. 根據運算符個數和限定的數值范圍隨機生成操作數
  4. 根據生成的表達式求解答案,求解過程發現表達式不合理就返回null
  5. 上一步合法的表達式,根據查重集合檢查是否重復,如果不合法,返回第1步
  6. 上一步合法的表達式,加入待輸出隊列和查重集合,如果重復,返回第1步
  7. 循環以上1-6,直到生成指定數量的題目

3.2.3 批卷功能代碼

/**
* 解析題目文件和答案文件
*/
public List<Exercises> readFile(File exercisesFile, File answerFile) throws Exception {
        BufferedReader exercisesReader = FileUtil.getBufferedReader(exercisesFile);
        BufferedReader answerReader = FileUtil.getBufferedReader(answerFile);
        String question;
        List<Exercises> exercisesList = new LinkedList<>();
        while ((question = exercisesReader.readLine()) != null) {
            String answer = answerReader.readLine();
            //解析成Exercises對象
            Exercises exercises = CalculateUtil.parseExercises(question);
            //填入學生答案
            exercises.setStudentAnswer(CalculateUtil.parseAnswer(answer));
            exercisesList.add(exercises);
        }
        return exercisesList;
 }
/**
 * 批卷功能
 */
public Result checkAnswer(List<Exercises> exercises) throws IOException {
        Result result = new Result();
        for (Exercises e : exercises) {
            generateAnswer(e);
            //填入表格
            CheckController.CHECK_EXERCISES_OBSERVABLE_LIST.add(e);
            if (e.getAnswer().equalsIgnoreCase(e.getStudentAnswer())) {
                result.getCorrectList().add(e.getNumber());
            } else {
                result.getWrongList().add(e.getNumber());
            }
        }
        //將結果輸出到文件
        writeCheckResultToFile(result);
        return result;
}

實現思路

  1. 讀取題目文件和學生答案,逐行解析為題目和答案
  2. 將解析出的題目和學生答案加入集合
  3. 從集合取出題目,計算正確答案
  4. 比較正確答案和學生答案
  5. 若上一步答案正確,把題號加入正確題目集合,若錯誤,把題號加入錯誤題目集合
  6. 循環以上1-5,直達文件讀取完畢

3.2.3 四則運算功能代碼

    /**
     * 生成答案
     */
    @Override
    public void generateAnswer(Exercises e) {
        Queue<String> queue = new LinkedList<>();
        ArrayList<String> eValueList = e.getValueList();

        //將所有運算數進隊列
        for (int i = 2 * e.getOperatorNumber(); i > e.getOperatorNumber() - 1; i--) {
            queue.add(eValueList.get(i));
        }

        //取出每個運算符,再從隊列取出兩個數字進行運算,結果再放入隊尾中,直到取完所有運算符,此時隊列中的數字為最終答案
        for (int i = e.getOperatorNumber() - 1; i >= 0; i--) {
            String opSymbol = eValueList.get(i);

            //從隊列取出兩個數字
            String num1 = queue.remove();
            String num2 = queue.remove();
            //計算兩數運算后結果
            String answer = OperatorEnum.getEnumByOpSymbol(opSymbol).op(num1, num2);

            //計算過程出現不符合條件的數值,就返回null
            if (answer == null) {
                e.setAnswer(null);
                return;
            }
            queue.add(answer);
        }
        e.setAnswer(queue.remove());
    }

實現思路:

  1. 將題目的所有操作數加入隊列
  2. 如果還有剩余的運算符,取出題目的一個運算符,如果沒有,跳到第7步
  3. 從隊列頭部取出兩個操作數
  4. 將2和3取出的操作數和運算進行四則運算
  5. 如果四則運算返回的結果為null,則結束,返回答案為null
  6. 將4的結果加入到隊列中,返回第2步
  7. 取出隊列中元素作為答案返回(此時只剩一個元素)

四、效能分析

4.1 程序效能

Snipaste_2020-04-12_12-02-35

這是先后兩次執行生成50000道題目功能時程序的資源消耗情況,可以得到以下結論:

  • 內存占用:剛開始執行功能時占用內存短暫上升,但在功能執行結束后很快觸發了GC,內存得到回收
  • 線程消耗:第一次執行時創建了線程,第二次執行時沒有創建新線程,說明線程池中線程得到重用
  • 線程阻塞:整個過程只出現一次短暫的線程阻塞
  • CPU占用:整個任務過程中CPU負載較高,屬於CPU密集型應用

4.2 性能優化

以下測試皆為程序執行生成10000道題目時的性能表現

4.2.1 優化前:(執行過程消耗時間為:74s)

image-20200411160540375

這是優化前的程序執行生成10000道題目的性能表現,可以看到程序中性能消耗最大的函數是GenerateServiceImpl類的validate函數,其中執行List.contains方法的時間占用達到了96%

4.2.2 查重算法優化后:(執行過程消耗時間為:11s)

image-20200412124126667

由於查重算法中,把題目是否重復的判斷寫在equals方法中,每次比較都要重新分析題目的運算次序,並且validate方法中直接調用了List.contain方法,從源碼來看,contain的內部是逐個遍歷再調用equals方法,效率很低,因此改成每次生成題目后,解析出題目的最簡式,用一個HashSet去保存題目的最簡式,查重時調用Set.contain方法,其內部實現時哈希尋址,因此效率得到提高。改進查重方法后,validate依然是消耗最大的函數,但占比已經下降到27%.

4.2.3 多線程並發優化后(執行過程消耗時間為:1s)

這次改進在查重算法改進的基礎至上,將生成題目和輸出到文件的方法進行分離,引入線程池和多線程並發執行,最終將生成10000道題的時間消耗降低至1s

五、測試報告

5.1 測試項:生成題目和答案是否符合要求

image-20200412131120729

結果說明:

以上為測試生成10道數值在2以內的題目的截圖,可以看到題目符合要求且答案正確

5.2 測試項:批卷功能是否正確判斷答案正確與否

修改上一步中輸出的Answer.txt中奇數題號的答案,再使用批卷功能

image-20200412131251036

查看輸出的Grade.txt文件:

image-20200412131347877

結果說明:

其中標出來奇數編號被修改的題目為錯誤題號,結果符合預期

5.3 測試項:能否支持一萬道以上大量題目的生成

在出題模式下執行生成10000道數值范圍在5以內的題目的功能

image-20200412131513215

查看生成的Exercises.txt文件

image-20200412131544099

查看生成的Answer.txt文件

image-20200412131608853

結果說明:

可以看到程序正確地執行了生成10000道題目的功能,消耗時間1s

六、PSP表格

PSP2.1 Personal Software Process Stages 預估耗時(分鍾) 實際耗時(分鍾)
Planning 計划 30 30
· Estimate · 估計這個任務需要多少時間 30 30
Development 開發 1755 2145
· Analysis · 需求分析 (包括學習新技術) 130 180
· Design Spec · 生成設計文檔 60 35
· Design Review · 設計復審 (和同事審核設計文檔) 5 5
· Coding Standard · 代碼規范 (為目前的開發制定合適的規范) 10 5
· Design · 具體設計 200 120
· Coding · 具體編碼 1200 1500
· Code Review · 代碼復審 30 120
· Test · 測試(自我測試,修改代碼,提交修改) 120 180
Reporting 報告 85 130
· Test Report · 測試報告 60 30
· Size Measurement · 計算工作量 10 10
· Postmortem & Process Improvement Plan · 事后總結, 並提出過程改進計划 15 90
合計 1870 2305

七、總結

獲得的經驗:

1.實現的過程中到了數據結構的知識,加深對數據結構的理解

2.對項目進行性能優化,加深對多線程知識的理解

3.增加了協作開發的經驗

不足的地方:

用Java來實現數據結構的效率不夠高,GUI界面沒有做響應式編程


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM