1. 程式人生 > 實用技巧 >為 CmakeLists.txt 新增 boost 元件

為 CmakeLists.txt 新增 boost 元件

這個作業屬於哪個課程 https://edu.cnblogs.com/campus/gdgy/Networkengineering1834
這個作業要求在哪裡 https://edu.cnblogs.com/campus/gdgy/Networkengineering1834/homework/11148
這個作業的目標 學會結對合作,與隊友協商共同實現一個自動生成小學四則運算題目的程式

結對成員

  • 魏龍濤 3118005337
  • 吳曉璇 3218005355

github倉庫

結對程式設計作業 —— 小學四則運算


PSP 表格

PSP2.1 Personal Software Process Stages 預估耗時(分鐘) 實際耗時(分鐘)
Planning 計劃 20 20
· Estimate · 估計這個任務需要多少時間 10 10
Development 開發 600 500
· Analysis · 需求分析 (包括學習新技術) 20 20
· Design Spec · 生成設計文件 10 10
· Design Review · 設計複審 10 10
· Coding Standard · 程式碼規範 (為目前的開發制定合適的規範) 10 10
· Design · 具體設計 30 30
· Coding · 具體編碼 400 460
· Code Review · 程式碼複審 30 30
· Test · 測試(自我測試,修改程式碼,提交修改) 30 30
Reporting 報告 80 65
· Test Repor · 測試報告 20 40
· Size Measurement · 計算工作量 20 15
· Postmortem & Process Improvement Plan · 事後總結, 並提出過程改進計劃 30 40
合計 1320 1290

效能分析

查重需求改進

最初的想法是每次生成一個新的運算式都去和已經生成的運算式集合(已經判斷過)進行比較,看是否重複。但如此一來字串的操作就會多得多。因此可以在判斷是否重複之前先判斷答案是否一致。答案一致運算式才有可能出現重複。因此改進後的查重邏輯為——

  1. 生成運算式並計算其結果
  2. 該式子與式子集合依次比較。看答案是否一致
    1. 不一致,將該式子加入到集合中
    2. 一致,繼續後續判斷
  3. 判斷答案一致的兩個運算式的長度是否一致(包括運算數、運算子以及括號)
    1. 不一致,將該式子加入到集合中
    2. 一致,繼續後續判斷
  4. 判斷兩個運算式的字元是否完全一致
    1. 不一致,將該式子加入到集合中
    2. 一致,跳過(捨棄)該運算式

如此可以減少很多後續的字串判斷邏輯,雖然也可能會"濫殺無辜",一些並未重複的式子會被捨棄,但也最大程度地保證了式子的唯一性以及演算法的執行速度。

效能分析

這裡的測試用例是生成10000道題目,式子中最大值為10。(能生成10000道題目是專案需求)

單元測試程式碼:

@SpringBootTest
@RunWith(SpringRunner.class)
class BuilderApplicationTests {

    @Autowired
    private GenerateService generateService;

    @Test
    void testGenerate() {
        generateService.generateList(10000, 10);
    }

}

JProfiler測試結果如下圖所示:


設計實現過程

  • 專案結構
  • 模組分析
    • Fraction
    • Result
  • 類分析
    • Calculate

    • Fraction


    • Generate

    • Grade

程式碼說明

public Fraction calculateFra(List fraSymList) {
    Stack tempOperator = new Stack(); // 臨時儲存運算子和 (
    List operator = new ArrayList(); // 儲存數值和運算子

    int len = fraSymList.size(); // 數字和運算子的總長度
    int times = 0; // 遍歷次數

    while (times < len) {
        if (fraSymList.get(times) instanceof Fraction) {
            Fraction chara = (Fraction) fraSymList.get(times);
            operator.add(chara);
        } else {
            String chara = (String) fraSymList.get(times);
            switch (chara) {
                case "(":
                    tempOperator.push(chara); // 將 ( 入棧
                    break;
                case ")":
                    while (tempOperator.peek() != "(") { // 迴圈查詢 ( 並返回,但不刪除
                        operator.add(tempOperator.pop()); // 將除 ) 外的運算子存入 operator 中
                    }
                    tempOperator.pop(); // 彈出 ( ,丟棄括號 ()
                    break;
                case "+":
                case "-":
                    while (!tempOperator.empty() && tempOperator.peek() != "(") {
                        operator.add(tempOperator.pop());
                    }
                    tempOperator.push(chara);
                    break;
                case "*":
                case "/":
                    while (!tempOperator.empty() && tempOperator.peek().toString().matches("[*/]")) {
                        operator.add(tempOperator.pop());
                    }
                    tempOperator.push(chara);
                    break;
                default:
                    operator.add(chara);
                    break;
            }
        }
        times++;
    }

    while (!tempOperator.empty()) {
        operator.add(tempOperator.pop());
    }

    Stack<Fraction> tempFrac = new Stack<Fraction>();
    for (int i = 0; i < operator.size(); i++) {
        if (operator.get(i) instanceof Fraction) { // 先將數字存入棧中
            tempFrac.push((Fraction) operator.get(i));
        } else { // 遇到運算子,從棧中取出兩個數字進行運算
            Fraction result;
            if (operator.get(i).equals("+")) {
                result = tempFrac.pop().add(tempFrac.pop());
            } else if (operator.get(i).equals("-")) {
                Fraction pop1 = tempFrac.pop();
                Fraction pop2 = tempFrac.pop();
                result = pop2.sub(pop1);
                if (result == null) {
                    // 減法運算返回null,說明是負數,直接返回null
                    return null;
                }
            } else if (operator.get(i).equals("*")) {
                result = tempFrac.pop().muti(tempFrac.pop());
            } else {
                Fraction pop1 = tempFrac.pop();
                Fraction pop2 = tempFrac.pop();
                result = pop2.div(pop1);
                if (result == null) {
                    return null;
                }
            }
            tempFrac.push(result);
        }
    }
    return tempFrac.pop();
}
public Result generate(int maxLimit) {
    Random r = new Random();

    char[] c = {'+', '-', '*', '÷'};
    // 生成運算子的數量,題目要求不超過3個。這裡範圍為1~3
    int operaCount = r.nextInt(3) + 1;
    // 分數的個數,根據運算子個數來生成。例如1個運算子最多有兩個分數
    int fractionCount = r.nextInt(operaCount + 1) + 1;
    // 非分數的個數 = 運算子個數 + 1 - 分數的個數
    int unFractionCount = operaCount + 1 - fractionCount;
    // 存放分數的List集合
    List<Fraction> fractionList = new ArrayList<>();
    // 存放符號的List集合
    List<String> symbolList = new ArrayList<>();
    // 存放運算數和運算子
    List fraSymList = new ArrayList();
    for (int i = 0; i < operaCount; i++) {
        // 遍歷運算子個數,一個運算子生成一個數
        symbolList.add(String.valueOf(c[r.nextInt(4)]));
    }
    for (int i = 0; i < fractionCount; i++) {
        // 遍歷分數的個數
        fractionList.add(new Fraction(true, maxLimit));
    }
    // 剩下的就是非分數
    if (unFractionCount >= 0) {
        for (int i = 0; i < unFractionCount; i++) {
            fractionList.add(new Fraction(false, maxLimit));
        }
    }

    // 運算數的個數 = 符號個數 + 1 即 fractionList.size() = symbolList.size() + 1
    // 因此取其一遍歷即可
    int j = 0;
    for (int i = 0; i < symbolList.size(); i++) {
        Fraction fraction = fractionList.get(i);
        String symbol = symbolList.get(i);
        fraSymList.add(fraction);
        fraSymList.add(symbol);
        j++;
    }
    // 拼接最後一個運算數
    Fraction lastFraction = fractionList.get(j);
    fraSymList.add(lastFraction);

    // 將除號轉換為計算用的/
    for (int i = 0; i < fraSymList.size(); i++) {
        if (i % 2 != 0) {
            if (fraSymList.get(i).equals("÷")) {
                fraSymList.set(i, "/");
            }
        }
    }
    if (!fractionService.isRule(fraSymList)) {
        // 不符合規則,返回null
        return null;
    }
    List newFraSymList = addBracket(fraSymList);
    Fraction resultFra = calculateService.calculateFra(newFraSymList);

    if (resultFra == null) {
        return null;
    }
    String expression = "";
    for (int i = 0;i<newFraSymList.size();i++) {
        if (newFraSymList.get(i) instanceof Fraction) {
            Fraction fraction =(Fraction) newFraSymList.get(i);
            // 如果是運算數
            expression = expression + fraction.toString() + " ";
        } else {
            // 如果是運算子
            expression = expression + newFraSymList.get(i) + " ";
        }
    }
    String trimExpression = expression.trim();
    return new Result(trimExpression, resultFra.toString());
}

生成運算式的流程為:

  1. 根據隨機數判斷預生成的運算子個數
  2. 根據運算子個數求得運算數的個數
    1. 運算數有正整數和分數的區分,因此也通過隨機數的方式對應生成合理的個數
  3. 利用迴圈遍歷拼接表示式String字串
  4. 判斷表示式是否合法
    1. 除數是否為0
  5. 計算答案
    1. 與此同時判斷運算過程中是否出現負數
  6. 將表示式和結果封裝成包裝類

測試執行

執行步驟

  1. 執行主啟動類BuilderApplication啟動SpringBoot工程
  2. 開啟瀏覽器在位址列中輸入lolcalhost:8080開啟四則運算生成的圖形化介面
  3. 在兩個輸入框中輸入引數,點選【生成運算】,按入參在頁面下方生成預期的結果
  4. 緊接著在專案根目錄會生成
    1. Exercises.txt:題目檔案
    2. Answers.txt:答案檔案
  5. 在每道題目後面的輸入框中輸入答案後點擊【提交答案】
    1. 彈窗顯示答題效果
    2. 在專案根目錄生成Grade.txt:答題情況檔案

介面展示

  • 視覺化介面展示

  • 生成運算

  • 執行結果


專案小結

魏龍濤

結對感受:寫專案有個得力隊友協作可真是太棒了——輕輕鬆鬆過五關斬六將。不懂就問,不會就說,人狠話不多。總之這次結對專案做下來倒沒什麼坎坷,想寫的時候就寫寫,寫著寫著就ok了,ddl什麼的當它不存在,果然是船到橋頭自然直。還是個人專案頭疼些,可能是我們兩實力都不差哈哈。

吳曉璇閃光點:敢於打破題目限定的框框條條,發揮自己在前端方面的優勢,自行開發簡約美觀的頁面來生成題目、展示題目和作答。使得這次結對專案多了一分"靈氣",至少比別人的好看多了~ 顏值加分好吧。u1s1,會寫程式碼就是不一樣,頁面開發和許多問題都自個兒解決了,我也不得不做快些了。

建議:少點古靈精怪,少些無中生有的BUG o(╥﹏╥)o 還有:會寫你就多寫點!