GitHub地址:https://github.com/TaoTaoLv1/arithmetic
結對伙伴:葉文濤
項目要求:
實現一個自動生成小學四則運算題目的命令行程序.
- 使用 -n 參數控制生成題目的個數(完成)
- 使用 -r 參數控制題目中數值的范圍, 。該參數可以設置為1或其他自然數。(完成)
- 生成的題目中計算過程不能產生負數(完成)
- 生成的題目中如果存在形如e1 ÷ e2的子表達式,那么其結果應是真分數。(完成)
- 程序一次運行生成的題目不能重復,生成的題目存入執行程序的當前目錄下的Exercises.txt文件(完成)
- 每道題目中出現的運算符個數不超過3個(完成)
- 在生成題目的同時,計算出所有題目的答案,並存入執行程序的當前目錄下的Answers.txt文件(完成)
- 程序應能支持一萬道題目的生成。(完成)
- 程序支持對給定的題目文件和答案文件,判定答案中的對錯並進行數量統計。(完成)
設計:
分為兩個部分,第一是生成題目,生成表達式及答案后寫入當前目錄下的文件,第二是判斷答案是否正確,讀取文件后判斷,再將結果寫入當前目錄下的文件。
代碼
主函數
public static void main(String[] args){ System.out.println("請選擇功能:"); System.out.println(" 1. 四則運算生成器"); System.out.println(" 2. 答案對比"); System.out.print("請輸入你的選擇[1/2]:"); int choose = new Scanner(System.in).nextInt(); switch (choose){ case 1: ProducerController producerController = new ProducerController(); producerController.ConstructProblem();break; case 2: JudgeAnswerController judgeAnswerController = new JudgeAnswerController(); judgeAnswerController.start();break; default: System.out.println("輸入不正確,請輸入1或2");main(args);break; } }
整數生成器與真分數生成器函數
public String[] createProblem(int range){ Random random = new Random(); int operatorCount = 1 + random.nextInt(3); //隨機操作符的個數(1-3個) int operand[] = new int[operatorCount + 1]; //操作數個數 int[] operatorIndex = index(operatorCount, 4, random); for(int i = 0; i < operatorCount + 1; i++){ operand[i] = random.nextInt(range); } String formula = stitchingFormula(operatorCount, operand, operatorIndex); //計算結果 Calculator calculator = new Calculator(); int res = calculator.algorithm(formula); String formulaRes[] = new String[2]; if (res > 0){ formulaRes[0] = formula; formulaRes[1] = String.valueOf(res); }else { return createProblem(range); } return formulaRes; }
public String[] createProblem(int range){ Random random = new Random(); int operatorCount = 1 + random.nextInt(3); //操作符的個數1-3 CreateInteger create = new CreateInteger(); int[] operatorIndex = create.index(operatorCount,2, random); //操作符的下標 //生成第一個操作數 int[] coprimeNumber1 = createCoprimeNumbers(range, random); int x = coprimeNumber1[0]; int y = coprimeNumber1[1]; String s = shamToProperFraction(x, y); for(int i=0; i < operatorCount; i++){ //生成剩下的操作數 int[] coprimeNumber = createCoprimeNumbers(range, random); int numx = coprimeNumber[0]; int numy = coprimeNumber[1]; String currentOpreator = OPERATOR[operatorIndex[i]]; if(currentOpreator.equals("+")){ //加法 x = x * numy + y * numx; y = y * numy; }else { //減法 int count = 0; while(x * numy - y * numx < 0){ //差為負數 coprimeNumber = createCoprimeNumbers(range, random); numx = coprimeNumber[0]; numy = coprimeNumber[1]; count++; if (count >= 5){ numx = x - 1; numy = y; } } x = x * numy - y * numx; y = y * numy; } String num = shamToProperFraction(numx, numy); s += currentOpreator + num; } int greatFactor = greatFactor(x,y); x /= greatFactor; //最終結果化簡 y /= greatFactor; String res = shamToProperFraction(x, y); s += "="; String formulaRes[] = {s, res}; return formulaRes; } /** * 假分數轉化為真分數 * @param x 分子 * @param y 分母 * @return */ public String shamToProperFraction(int x, int y){ if (x > y){ int n = x / y; x = (x - n * y); if (x == 0){ return String.valueOf(n); } return n + "'" + x + "/" + y; }else if (x == y){ return "1"; }else if (y == 1){ return String.valueOf(x); }else if (x == 0){ return "0"; } return x + "/" + y; } }
計算結果函數
public int algorithm(String s) { Stack<Integer> numStack = new Stack<>(); //放數字 Stack<String> operatorStack = new Stack<>(); //放操作符 HashMap<String, Integer> hashMap = new HashMap<>(); //存放運算符優先級 hashMap.put("(", 0); hashMap.put("+", 1); hashMap.put("-", 1); hashMap.put("*", 2); hashMap.put("÷", 2); String formula = s.replaceAll(" ", ""); for (int i = 0; i < formula.length();) { StringBuilder digit = new StringBuilder(); //StringBuilder類中的方法主要偏重於對於字符串的變化,例如追加、插入和刪除等,這個也是StringBuffer和String類的主要區別。 char c = formula.charAt(i); //將式子字符串切割為c字符 while (Character.isDigit(c)) { //判斷字符是否為10進制數字,將一個數加入digit digit.append(c); i++; if (i < formula.length()){ c = formula.charAt(i); }else { break; } } if (digit.length() == 0){ //當前digit里面已經無數字,即當前處理符號 switch (c) { case '(': { operatorStack.push(String.valueOf(c));//如果是( 轉化為字符串壓入字符棧 break; } case ')': { //遇到右括號了計算,因為(的優先級最高 String stmp = operatorStack.pop(); //如果是),將符號棧棧頂元素取到 while (!operatorStack.isEmpty() && !stmp.equals("(")) { //當前符號棧里面還有+ - * / int a = numStack.pop(); //取操作數a,b int b = numStack.pop(); int result = calculate(b, a, stmp); //計算 if(result < 0) return -1; numStack.push(result); //將結果壓入棧 stmp = operatorStack.pop(); //符號指向下一個計算符號 } break; } case '=': { //遇到等號了計算 String stmp; while (!operatorStack.isEmpty()) { //當前符號棧里面還有+ - * /,即還沒有算完 stmp = operatorStack.pop(); int a = numStack.pop(); int b = numStack.pop(); int result = calculate(b, a, stmp); if(result < 0) return -1; numStack.push(result); } break; } default: { //不滿足之前的任何情況 String stmp; while (!operatorStack.isEmpty()) { //如果符號棧有符號 stmp = operatorStack.pop(); //當前符號棧,棧頂元素 if (hashMap.get(stmp) >= hashMap.get(String.valueOf(c))) { //比較優先級 int a = numStack.pop(); int b = numStack.pop(); int result =calculate (b, a, stmp); if(result < 0) return -1; numStack.push(result); } else { operatorStack.push(stmp); break; } } operatorStack.push(String.valueOf(c)); //將符號壓入符號棧 break; } } } else { //處理數字,直接壓棧 numStack.push(Integer.valueOf(digit.toString())); //Integer.valueof()返回的是Integer對象,而Integer.parseInt()返回的是int型 continue; //結束本次循環,回到for語句進行下一次循環,即不執行i++(因為此時i已經指向符號了) } i++; } return numStack.peek(); //返回棧底數字即等式的答案。 }
判斷結果函數
public void start(){ System.out.print("請輸入待驗證答案路徑:"); Scanner scanner = new Scanner(System.in); String exerciseFilePath = scanner.next(); System.out.print("請輸入程序生成答案文件路徑:"); String answerFilePath = scanner.next(); try { List<String> exerciseAnswers = exerciseFileReader(exerciseFilePath); List<String> answers = answerReader(answerFilePath); List<String> correct = new ArrayList<>(); List<String> wrong = new ArrayList<>(); int max = Math.max(exerciseAnswers.size(), answers.size()); int num = 1; for (int i = 0; i < max; i++){ if (exerciseAnswers.get(i).equals(answers.get(i))){ correct.add(String.valueOf(num++)); }else { wrong.add(String.valueOf(num++)); } } File grade = new File("Grade.txt"); if (grade.exists()){ grade.delete(); } if (grade.createNewFile()){ FileOutputStream gradeOutput = new FileOutputStream(grade); PrintStream gradePrintStream = new PrintStream(gradeOutput); String corrects = String.join(",", correct); gradePrintStream.println("Correct:" + correct.size() + " (" + corrects + ")"); String wrongs = String.join(",", wrong); gradePrintStream.println("Wrong:" + wrong.size() + " (" + wrongs + ")"); } System.out.println("判定完成"); } catch (FileNotFoundException e) { System.out.println("文件不存在"); } catch (IOException e) { System.out.println("文件讀入異常"); } } public List<String> exerciseFileReader(String path) throws IOException { BufferedReader exerciseReader = new BufferedReader(new FileReader(path)); String exerciseAnswer = ""; List<String> exerciseAnswers = new ArrayList<>(); while ((exerciseAnswer = exerciseReader.readLine()) != null){ String[] split = exerciseAnswer.split("="); if (split[1] != null){ exerciseAnswers.add(split[1]); }else { exerciseAnswers.add(" "); } } return exerciseAnswers; }
測試
1.隨機生成10道10以內的四則運算
2.判斷題目的正確與否
3.支持生成一萬道題目
由於題目過多,因此直接在連接中打開
以上就是所有功能的測試
PSP表格
PSP2.1 |
Personal Software Process Stages |
預估耗時(分鍾) |
實際耗時(分鍾) |
Planning |
計划 |
10 |
5 |
· Estimate |
· 估計這個任務需要多少時間 |
800 |
1200 |
Development |
開發 |
480 |
630 |
· Analysis |
· 需求分析 (包括學習新技術) |
60 |
30 |
· Design Spec |
· 生成設計文檔 |
60 |
80 |
· Design Review |
· 設計復審 (和同事審核設計文檔) |
30 |
45 |
· Coding Standard |
· 代碼規范 (為目前的開發制定合適的規范) |
30 |
30 |
· Design |
· 具體設計 |
30 |
60 |
· Coding |
· 具體編碼 |
120 |
360 |
· Code Review |
· 代碼復審 |
30 |
45 |
· Test |
· 測試(自我測試,修改代碼,提交修改) |
120 |
120 |
Reporting |
報告 |
120 |
120 |
· Test Report |
· 測試報告 |
60 |
30 |
· Size Measurement |
· 計算工作量 |
30 |
30 |
· Postmortem & Process Improvement Plan |
· 事后總結, 並提出過程改進計划 |
30 |
30 |
合計 |
1200 |
1610 |
總結
在這次編程作業中,我和葉文濤同學一起討論,他主要負責編寫代碼,后期的測試和報告則由我負責,在這一次的作業中,我也從伙伴身上學習到了很多,認識到自己與他人之間的差距,他在編程過程中也耐心的解答我的疑惑,在這個過程中不斷的完善代碼。兩個人一起處理問題,互相汲取對方好的想法,有些細節沒有考慮到的,另一個人可以幫忙補充,這樣使得效率也大大提高。例如在我們進行最后測試的過程中,我發現當計算答案時如果答案為空則會出錯,在這個情況下我們進行討論之后也克服了這個問題。