2016012034+小學四則運算練習軟件項目報告
代碼倉庫地址:https://coding.net/u/Siamese_miao/p/fourArithmetic/git?public=true
測試效果見src下生成的result.txt文件
一、 需求分析
1. 使用JAVA編程語言。
2. 接收一個輸入參數n,隨機產生n道四則運算練習題,符號用+-*÷來表示。
3. 每個數字在 0 和 100 之間。
4. 運算符在3個到5個之間並且每道練習題至少要包含2種運算符。
5. 運算過程中不得出現負數與非整數。
6. 將學號和生成的練習題及對應答案輸出到文件“result.txt”中,不輸出額外信息。
附加需求
1. 產生帶括號的四則運算並求解,算式中存在的括號必須大於2個,且不得超過運算符的個數。
2. 產生真分數的加減法運算,運算過程中都為最簡真分數。
二、 功能設計
基本功能
能夠根據用戶輸入的參數n隨機產生n道符合要求的四則混合運算練習題,自動算出答案,並將式子與答案以文檔的形式呈現。
擴展功能
支持有括號的運算、支持最簡真分數的加減運算
三、 設計實現
首先,我從簡單四則混合運算開始,接著在這的基礎上完成了分數運算,最後再思考帶括號的運算。我先按這個順序分別創建了項目,單獨編寫,最後才分類規整到一個項目中,如圖我分為5個類。
- Main類:主類,負責接收命令行的參數並啟動程序。
- fileCreate類:創建文件類,負責每次出題類型並產生result.text文件,將學號與練習題寫入文件。
- formula類:式子類,負責根據調用產生同種類型的式子,含有arithmetic(簡單四則運算)、bracket(帶括號的四則運算)、score(真分數加減運算)三種函數。
- calculate類:計算類,負責各種計算,含有結果運算、有條件產生減數、有條件產生除數、有條件產生分子、有條件產生分母、判斷數的大小、求取最大公因數、求取最小公倍數、分數相加、分數相減等10個方法。
各類與各函數之間的關系如下圖。
四、 算法詳解
我的算法大多數比較簡單,多采用遞歸的方式。
-
簡單四則運算
由於符號由+-*÷表示,而計算機計算使用+-*/,所以我先定義了兩個符號數組與定義兩個字符串,一個作為顯示式子用,一個用於計算,數組下標一一對應。再由範圍為3~5的隨機數確定式子的符號數,再由兩個整型數組存儲算數與符號對應的下標值,值皆由隨機數產生,算數範圍為0~100,下標值範圍為0~3。式子使用for循環,每次將一個算數與一個符號加入字符串中,執行完循環後,最後再加上最後一個算數。計算後,連同式子一並輸出。在計算時,我使用java調用js中的eval(String)函數求解。因為式子必須包含兩種運算符以上,在輸出前需要判斷是否所有符號相同,不相同則不輸出並重新運行程序。
1 // 計算結果 2 public static Object result(String temp) { 3 ScriptEngineManager sem = new ScriptEngineManager(); 4 ScriptEngine se = sem.getEngineByName("js"); 5 Object last = 0; 6 try { 7 last = se.eval(temp); 8 } catch (ScriptException e) { 9 e.printStackTrace(); 10 } 11 return last; 12 }計算結果
考慮到運算過程中不能出現小數與負數,所以在除號與減號部分需要做處理,當減數大於被減數時,重新生成減數直至不大於被減數,當除數無法整除被除數或為0時,重新生成除數。後來在測試時發現兩兩數字符合規則,然而整條式子卻仍會出現負數與小數。經過思考,一般出現於連減、連除、減號後乘除因優先級不同的式子中,所以我設了個判斷,減號後一位只能為加號,除號後一位只能為加減號,由此解決了運算過程中出現負數或小數的問題。
1 public static String arithmetic() { 2 int m, j; 3 char[] p = new char[] { ‘*‘, ‘+‘, ‘÷‘, ‘-‘ }; 4 char[] q = new char[] { ‘*‘, ‘+‘, ‘/‘, ‘-‘ }; 5 String temp1 = ""; 6 String temp2 = ""; 7 m = (int) (Math.random() * 3 + 3); // 符號數 8 int[] num = new int[m + 1]; // 數字 9 int[] key = new int[m]; // 符號所在的下標 10 for (j = 0; j <= m; j++) { 11 num[j] = (int) (Math.random() * 101); 12 } 13 for (j = 0; j < m; j++) { 14 if (j > 0 && key[j - 1] == 3) { // 減號後僅允許加號,防止負數出現 15 key[j] = 1; 16 } else if (j > 0 && key[j - 1] == 2) { 17 key[j] = (int) (Math.random() * 2); // 除號後僅允許乘號與加號,防止負數 18 } else { 19 key[j] = (int) (Math.random() * 4); // 隨機符號 20 } 21 temp1 += String.valueOf(num[j]) + String.valueOf(p[key[j]]); 22 temp2 += String.valueOf(num[j]) + String.valueOf(q[key[j]]); 23 if (key[j] == 3) { 24 num[j + 1] = calculate.decide1(num[j], num[j + 1]); // 選定小於被減數的減數 25 } else if (key[j] == 2) { 26 num[j + 1] = calculate.decide2(num[j], num[j + 1]); // 確保能夠整除 27 } 28 } 29 j = 0; 30 while (j < (m - 1) && key[j] == key[j + 1]) 31 j++; // 與第一個符號相同數 32 if (j == (m - 1)) 33 return arithmetic(); // 若所有符號相同,該式子不算,保證有兩種運算符 34 else { 35 temp1 += String.valueOf(num[m]); 36 temp2 += String.valueOf(num[m]); 37 return temp1 + "=" + calculate.result(temp2); 38 } 39 }簡單四則運算
-
分數的加減
在基本混合運算的基礎上修改出分數加減就顯得簡單許多。最主要的就是分數的通分約分,解決了這個問題,程序就基本完成了。因為沒有優先級的關系,可以一邊補充式子一邊計算從而修改算數,無需限定符號。這也是我最滿意的代碼。
1 // 真分數分式 2 public static String score() { 3 char[] p = new char[] { ‘+‘, ‘-‘ }; 4 int j; 5 String temp1 = ""; 6 int m = (int) (Math.random() * 3 + 3); 7 int[] key = new int[m]; // 運算符 8 int[] x = new int[m + 1]; // 分子 9 int[] y = new int[m + 1]; // 分母 10 int[] sum = new int[2];// 中途運算結果 11 for (j = 0; j <= m; j++) { 12 x[j] = (int) (Math.random() * 20 + 1); 13 y[j] = calculate.decide3(x[j]); 14 } 15 sum[0] = x[0]; 16 sum[1] = y[0]; 17 for (j = 0; j < m; j++) { 18 key[j] = (int) (Math.random() * 2); 19 if (key[j] == 0) { // 結果小於1 20 int[] num = new int[2]; 21 num = calculate.fracAdd(sum[0], sum[1], x[j + 1], y[j + 1]); 22 if (num[0] >= num[1]) { 23 key[j] = 1; 24 } else { 25 sum = num; 26 } 27 } 28 if (key[j] == 1) { // 結果不為負數 29 int[] num = new int[2]; 30 num = calculate.fracSub(sum[0], sum[1], x[j + 1], y[j + 1]); 31 if (num[0] < 0) { 32 x[j + 1] = calculate.decide4(sum[0], sum[1]); 33 y[j + 1] = sum[1]; 34 num = calculate.fracSub(sum[0], sum[1], x[j + 1], y[j + 1]); 35 } 36 sum = num; 37 } 38 temp1 += String.valueOf(x[j]) + "/" + String.valueOf(y[j]) + String.valueOf(p[key[j]]); 39 } 40 j = 0; 41 while (j < (m - 1) && key[j] == key[j + 1]) 42 j++; // 與第一個符號相同數 43 if (j == (m - 1)) 44 return score(); // 若所有符號相同,該式子不算,保證有兩種運算符 45 else { 46 temp1 += String.valueOf(x[m]) + "/" + String.valueOf(y[m]); 47 return temp1 + "=" + sum[0] + "/" + sum[1]; 48 } 49 }分數運算
-
含括號的運算
這部分由於優先級的改變以及時間關系,還沒有改善完成,目前仍無法確保運算結果不含小數與負數。
我思考了很久如何隨機生成括號,最後受到一篇博客(當時忘記保存網址,現在已經找不到了,抱歉)使用概率的啟發,我以一定概率的方式生成左括號,記錄生成左括號的個數以及未匹配的左括號的個數,以一定概率生成右括號,最後補齊。
1 if (((brack * 2) <= (n - 1)) && (((int) (Math.random() * 2)) == 0)) // 以一定概率生成左括號,概率為1/2 2 { 3 temp1 += "("; 4 temp2 += "("; 5 brack++; 6 brack_left++; 7 temp1 += num[++i]; // 生成左括號後必須生成一個數字和運算符,不然可能出現(15)這樣的錯誤 8 temp2 += num[i]; 9 op = (int) (Math.random() * 4); 10 temp1 += Op[op]; 11 temp2 += p[op]; 12 if (op == 3) 13 div = 1; 14 else if (op == 1) 15 div = 2; 16 }概率生成左括號
五、 測試運行
進入src文件下,輸入javac -encoding utf-8 Main.java 編譯出相應的class文件,再輸入java Main 20進行測試,我們可以先測試java Main abc或java Main 1500或java Main 0,在這裏我使用的jdk版本為jdk1.8.0_25。
測試結果如下圖。
除此之外,我還學習了使用myeclipse做單元測試,測試結果如圖所示,我從測試結果發現,當文件存在時,刪除重建會耗時約2秒(以5道算式為例)。
六、 總結
一開始由於我不清楚我能否完成附加功能,所以我用一個Main類的主函數完整的寫一個簡單的四則運算。但是調試時花費了許多時間,並且十分不靈活,所以我把函數中重復的計算部分抽離出來,寫成靜態方法,再進行測試,調試起來方便許多,想要修改哪一個部分的代碼只需要在相應的函數中修改即可。同理,分數運算與括號運算我也這麽做,由此我創建了三個項目。當三個項目都差不多完成時,我意識到其中有許多重復之處,所以我將它們分類並合在一個項目中。
除了主類包含了一個主函數外,我把我的程序分成3個類,各自分工,每個類中我盡量把各個功能細分成各種小方法,尤其在calculate類中每個方法不超過10行。具體可看第三點的關系圖。方法間的逐級調用給調試和測試都帶來了很多便利,尤其在測試優化方面,同時也增加了代碼的可移植性和可讀性。
七、 PSP
PSP2.1 |
任務內容 |
計劃共完成需要的時間(min) |
實際完成需要的時間(min) |
Planning |
計劃 |
30 |
20 |
· Estimate |
· 估計這個任務需要多少時間,並規劃大致工作步驟 |
30 |
20 |
Development |
開發 |
1515 |
2470 |
· Analysis |
· 需求分析 (包括學習新技術) |
180 |
150 |
· Design Spec |
· 生成設計文檔 |
50 |
30 |
· Design Review |
· 設計復審 (和同事審核設計文檔) |
10 |
15 |
· Coding Standard |
· 代碼規範 (為目前的開發制定合適的規範) |
5 |
5 |
· Design |
· 具體設計 |
10 |
15 |
· Coding |
· 具體編碼 |
1200 |
1800 |
· Code Review |
· 代碼復審 |
30 |
15 |
· Test |
· 測試(自我測試,修改代碼,提交修改) |
30 |
440 |
Reporting |
報告 |
75 |
170 |
· Test Report |
· 測試報告 |
10 |
15 |
· Size Measurement |
· 計算工作量 |
5 |
5 |
· Postmortem & Process Improvement Plan |
· 事後總結, 並提出過程改進計劃 |
60 |
150 |
這次的代碼我思考了很久,在敲代碼與測試方面遠遠的超過了計劃的時間,源於我低估了代碼的復雜度。雖然有思路,但把思路實現卻花費了特別多的時間,思慮的不充分總是讓程序報錯,再加上我對java掌握的不夠,所以我在編程花費了許多許多時間,有很多時候就是不知如何實現需求。再者,打這篇博客報告也花費了很多時間,但也再次整體的梳理了我的思路。
在這次打代碼的過程中,我學到了四個方法。
一個是使用java調用js的eval()函數,這個方法可以輸入字符串型的算式然後直接算出答案。
另外一個是在用命令運行符使用Java Main 20作為命令時,應使用
1 n=Integer.parseInt(args[0]);
來獲取輸入的參數,而我在myeclipse使用的一直是
1 Scanner in = new Scanner(System.in); 2 n=in.nextInt();
語句執行。
還有一個是以概率的方式隨機生成括號。
最後是使用Debug修改錯誤代碼,使用單元測試優化程序提高性能。
這次的學習讓我意識到自己的不足,我以後會繼續努力。
2016012034+小學四則運算練習軟件項目報告