Arithmetic
前言
GitHub地址
合作伙伴:林奇凱
?
項目簡介
- Arithmetic是一個能夠自動生成小學四則運算題目的命令行程序
項目需求
- 使用 -n 參數控制生成題目的個數,例如:
Myapp.exe -n 10 - 使用 -r 參數控制題目中數值(自然數、真分數和真分數分母)的范圍,例如:
Myapp.exe -r 10
- 生成的題目中計算過程不能產生負數,也就是說算術表達式中如果存在形如e1?? e2的子表達式,那么e1?≥ e2。
- 生成的題目中如果存在形如e1?÷ e2的子表達式,那么其結果應是真分數。
- 每道題目中出現的運算符個數不超過3個。
- 程序一次運行生成的題目不能重復,即任何兩道題目不能通過有限次交換+和×左右的算術表達式變換為同一道題目。例如,23 + 45 =?和45 + 23 = 是重復的題目,6?×?8 = 和8?× 6 = 也是重復的題目。3+(2+1)和1+2+3這兩個題目是重復的,由于+是左結合的,1+2+3等價于(1+2)+3,也就是3+(1+2),也就是3+(2+1)。但是1+2+3和3+2+1是不重復的兩道題,因為1+2+3等價于(1+2)+3,而3+2+1等價于(3+2)+1,它們之間不能通過有限次交換變成同一個題目。
-
生成的題目存入執行程序的當前目錄下的Exercises.txt文件,格式如下:?
1. 四則運算題目1
2. 四則運算題目2
……
其中真分數在輸入輸出時采用如下格式,真分數五分之三表示為3/5,真分數二又八分之三表示為2’3/8 -
在生成題目的同時,計算出所有題目的答案,并存入執行程序的當前目錄下的Answers.txt文件,格式如下:
1. 答案1
2. 答案2
特別的,真分數的運算如下例所示:1/6 + 1/8 = 7/24。 - 程序應能支持一萬道題目的生成。
-
程序支持對給定的題目文件和答案文件,判定答案中的對錯并進行數量統計,輸入參數如下:
Myapp.exe -e <exercisefile>.txt -a <answerfile>.txt
-
?統計結果輸出到文件Grade.txt,格式如下:
Correct: 5 (1, 3, 5, 7, 9)
Wrong: 5 (2, 4, 6, 8, 10)
其中“:”后面的數字5表示對/錯的題目的數量,括號內的是對/錯題目的編號。為簡單起見,假設輸入的題目都是按照順序編號的符合規范的題目。
開發中遇到的困難
- 運算優先級問題以及算式輸出時括號添加問題:
一開始是用char型來存運算符,后來考慮到優先級問題,決定建一個運算符的類——Operator。這個類有兩個成員變量,一個就是char型的運算符,另一個就是int型的優先級了,而這個優先級與運算符生成的個數有關,所以在new一個運算符對象時要調用帶參構造函數,并傳入存有運算符個數的參數。對于括號問題,我是根據優先級來加括號的。
-
什么樣的兩個問題算是重復?
一開始我以為如果題目A能通過交換律、結合律變換后得到題目B,那么AB就算重復。如:1+2+3=1+(2+3)=1+(3+2)=(1+3)+2=(3+1)+2=3+(1+2)=3+(2+1)=3+2+1,即1+2+3與3+2+1算是重復。如果是這樣的話,就只需要判斷兩道題的運算數、運算符和運算結果是否都一樣就行了。
后來返回看需求時發現1+2+3與3+2+1不算重復。仔細看才發現1+2+3=3+3=6,而3+2+1=5+1=6,雖然最終結果相同,但是計算過程不同。于是,在原基礎上,我在Question類添加了一個用來存過程結果的列表results。這樣兩個問題在運算數、運算符和運算結果都一樣的基礎上,如果過程結果一一對應,才算是重復。
設計思路
PSP
| PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
| Planning | 計劃 | ?60 | ?60 |
| · Estimate | · 估計這個任務需要多少時間 | ?60 | ?60 |
| Development | 開發 | 660 | ?780 |
| · Analysis | · 需求分析 (包括學習新技術) | ?90 | ?100 |
| · Design Spec | · 生成設計文檔 | ?30 | ?30 |
| · Design Review | · 設計復審 (和同事審核設計文檔) | ?30 | ?30 |
| · Coding Standard | · 代碼規范 (為目前的開發制定合適的規范) | ?30 | ?30 |
| · Design | · 具體設計 | ?60 | ?60 |
| · Coding | · 具體編碼 | ?240 | ?320 |
| · Code Review | · 代碼復審 | ?60 | ?90 |
| · Test | · 測試(自我測試,修改代碼,提交修改) | ?120 | ?120 |
| Reporting | 報告 | ?60 | ?90 |
| · Test Report | · 測試報告 | ?30 | ?30 |
| · Size Measurement | · 計算工作量 | ?20 | ?30 |
| · Postmortem & Process Improvement Plan | · 事后總結, 并提出過程改進計劃 | ?10 | ?30 |
| 合計 | ? | ?780 | ?930 |
?
關鍵代碼
- 分數部分
public class Fraction {int n=0; //整數部分int molecular=0; //分子int denominator=1; //分母/*** 帶參構造方法,用于隨機生成分數* @param scope 給定分子分母的范圍*/public Fraction(int scope) {Random r=new Random();molecular=r.nextInt(scope)+1;denominator=r.nextInt(scope)+1;rationalize();}/*** 帶參構造方法,用于賦值* @param n 整數部分* @param molecular 分子* @param denominator 分母*/public Fraction(int n,int molecular,int denominator) {this.molecular=molecular;this.denominator=denominator;this.n=n;rationalize();}/*** 無參構造方法,生成一個數(默認值為0,即0'0/1)*/public Fraction() {}/*** 分數有理化*/public void rationalize() {//假分數化為帶分數if(molecular>=denominator){n+=molecular/denominator;molecular%=denominator;}//約分int gcd=Util.gCommonDivisor(molecular,denominator);if(gcd!=1) {molecular/=gcd;denominator/=gcd;}}/*** 通分* @param lcm 最小公倍數*/public void commonReduction(int lcm) {molecular*=lcm/denominator;denominator=lcm;}/*** 帶分數化為假分數,用于計算*/public void changeToImproperFraction() {if(n==0) return;molecular+=n*denominator;n=0;}/*** 判斷數值是否為0* @return 為0返回true,否則返回false*/public boolean isZero() {if(n==0&&molecular==0) return true;return false;}@Overridepublic String toString() {rationalize();if(molecular%denominator==0) return ""+n;String fraction="";if(n>0) fraction+=n+"\'";fraction+=molecular;if(denominator>1) fraction+="/"+denominator;return fraction;} }
?
- 運算符部分
ublic class Operator {char operator; //運算符int priority; //優先級(1最高)/*** 帶參構造方法,用于隨機生成運算符* @param n 運算符個數,用于生成優先級*/public Operator(int n) {Random r=new Random();switch(r.nextInt(4)){case 0:operator='+'; //43break;case 1:operator='-'; //45break;case 2:operator='×'; //215break;default:operator='÷'; //247 }priority=r.nextInt(n)+1;}/*** 帶參構造方法,用于指定運算符* @param c 運算符* @param i 優先級*/public Operator(char c,int i) {operator=c;priority=i;}/*** 判斷優先級是否重復* 運算符列表的最后一項與前面所有項進行比較,如果出現優先級重復,則刪掉最后一項* @param list 運算符列表* @return 重復返回true,否則返回false*/public static boolean isRepeated(List<Operator> list) {for(int i=0;i<list.size()-1;i++) if(list.get(list.size()-1).priority==list.get(i).priority){list.remove(list.size()-1);return true;}return false;}@Overridepublic String toString() {return operator+" : "+priority;} }
?
- 運算題目方面
public class Question {int n=new Random().nextInt(3)+1; //隨機生成題目運算符的個數(1~3)String question=""; //題目List<Fraction> fractions=new ArrayList<Fraction>(); //生成的所有運算數組成的列表(用于計算方法calculate())List<Operator> operators=new ArrayList<Operator>(); //生成的所有運算符組成的列表(用于計算方法calculate())List<Fraction> results=new ArrayList<Fraction>(); //題目每一步運算得出的結果組成的列表Fraction[] fTemp=new Fraction[n+1]; //生成的所有運算數組成的數組(用于生成題目)Operator[] oTemp=new Operator[n]; //生成的所有運算符組成的數組(用于生成題目)Fraction result; //題目得出的最終結果/*** 帶參構造方法,用于隨機生成題目* @param scope 給定數值的范圍*/public Question(int scope) {do{question="";fractions.clear();operators.clear();results.clear();Fraction f;Operator o;f=new Fraction(scope);fractions.add(f);fTemp[0]=f;for(int i=0;i<n;i++){do{o=new Operator(n);operators.add(o);if(i==0) break;}while(Operator.isRepeated(operators)); //用于生成優先級不重復的運算符oTemp[i]=o;f=new Fraction(scope);fractions.add(f);fTemp[i+1]=f;}result=calculate(); //計算}while(result.n==-1); //如果結果為-1,證明題目不合法,需重新設置題目 }/*** 帶參構造方法,用于指定題目* @param fs 運算數數組* @param os 運算符數組*/public Question(Fraction[] fs,Operator[] os) {fTemp=fs;oTemp=os;for(Fraction f:fs) fractions.add(f);for(Operator o:os) operators.add(o);result=calculate();}/*** 計算* @return 返回最終結果,如果結果為-1,則證明題目非法* 題目非法的原因有二:* 1.被減數小于減數* 2.除數為0*/public Fraction calculate() {int priority=1;do{for(int i=0;i<operators.size();i++)if(operators.get(i).priority==priority){switch(operators.get(i).operator){case '+':fractions.set(i,Util.add(fractions.get(i),fractions.get(i+1)));break;case '-'://如果減數大于被減數,則返回-1if(!Util.compare(fractions.get(i),fractions.get(i+1))) return new Fraction(-1,0,1);fractions.set(i,Util.minus(fractions.get(i),fractions.get(i+1)));break;case '×':fractions.set(i,Util.multiply(fractions.get(i),fractions.get(i+1)));break;case '÷'://如果除數為0,則返回-1if(fractions.get(i+1).isZero()) return new Fraction(-1,0,1);fractions.set(i,Util.divice(fractions.get(i),fractions.get(i+1)));break;}results.add(fractions.get(i));fractions.remove(i+1);operators.remove(i);break;}priority++;}while(operators.size()>0);return fractions.get(0);}/*** 判斷題目是否重復* 題目列表的最后一項與前面所有項進行比較,如果出現題目重復,則刪掉最后一項* @param list 題目列表* @return 重復返回true,否則返回false* 判斷題目重復的依據有二:* 1.運算數相同,運算符相同* 2.運算順序相同,即結果列表一一對應*/public static boolean isRepeated(List<Question> list) {for(int i=0;i<list.size()-1;i++){//最終結果不同if(!Util.isEqual(list.get(list.size()-1).result,list.get(i).result)) return false;//結果列表大小不同if(list.get(list.size()-1).results.size()!=list.get(i).results.size()) return false;//結果列表一一對應存在不同元素for(int j=0;j<list.get(list.size()-1).results.size();j++)if(!Util.isEqual(list.get(list.size()-1).results.get(j),list.get(i).results.get(j))) return false;boolean bool=false;//數值列表存在不同元素for(Fraction f1:list.get(list.size()-1).fTemp){for(Fraction f2:list.get(i).fTemp){if(!Util.isEqual(f1,f2)) bool=false;else{bool=true;break;}}if(bool==false) return false;}//符號列表存在不同元素for(Operator o1:list.get(list.size()-1).oTemp){for(Operator o2:list.get(i).oTemp){if(!Util.isEqual(o1,o2)) bool=false;else{bool=true;break;}}if(bool==false) return false;}}list.remove(list.size()-1);return true;}@Overridepublic String toString() {int i1=0,i2=0,i3=0;for(;i1<oTemp.length;i1++)if(oTemp[i1].priority==1){question+=fTemp[i1]+" "+oTemp[i1].operator+" "+fTemp[i1+1];break;}if(oTemp.length>1){for(;i2<oTemp.length;i2++)if(oTemp[i2].priority==2){if(i2==i1-1) question=fTemp[i2]+" "+oTemp[i2].operator+" ( "+question+" )";if(i2==i1+1) question="( "+question+" ) "+oTemp[i2].operator+" "+fTemp[i2+1];if(oTemp.length==3){for(;i3<oTemp.length;i3++)if(oTemp[i3].priority==3){if(i3==0) question=fTemp[i3]+" "+oTemp[i3].operator+" [ "+question+" ]";if(i3==1){if(i1==1) question="( "+fTemp[i1]+" "+oTemp[i1].operator+" ) "+oTemp[i3].operator+" ( "+fTemp[i2]+" "+oTemp[i2].operator+" )";if(i2==1) question="( "+fTemp[i2]+" "+oTemp[i2].operator+" ) "+oTemp[i3].operator+" ( "+fTemp[i1]+" "+oTemp[i1].operator+" )";}if(i3==2) question="[ "+question+" ] "+oTemp[i3].operator+" "+fTemp[i3+1];break;}}break;}}question+=" = ";return question;} }
?
- Util類基本方法
public class Util {/*** 最大公因數,用于約分* 遞歸實現輾轉相除法* @param a* @param b* @return 返回a、b的最大公因數*/public static int gCommonDivisor(int a,int b) {if(b==0) return a;else return gCommonDivisor(b,a%b);}/*** 最小公倍數,用于通分* [a,b]=a*b/(a,b)* @param a* @param b* @return 返回a、b的最小公倍數*/public static int lCommonMultiple(int a,int b) {return a*b/gCommonDivisor(a,b);}/*** 判斷a、b的大小,用于判斷兩數相減時被減數是否不小于減數* @param a* @param b* @return a>=b返回true,否則返回false*/public static boolean compare(Fraction a,Fraction b) {a.changeToImproperFraction();b.changeToImproperFraction();if(a.denominator!=b.denominator){int lcm=lCommonMultiple(a.denominator,b.denominator);a.commonReduction(lcm);b.commonReduction(lcm);}if(a.molecular>=b.molecular) return true;else return false;}/*** a+b* @param a* @param b* @return 返回a+b的結果*/public static Fraction add(Fraction a,Fraction b) {if(a.denominator!=b.denominator){int lcm=lCommonMultiple(a.denominator,b.denominator);a.commonReduction(lcm);b.commonReduction(lcm);}Fraction c=new Fraction(a.n+b.n,a.molecular+b.molecular,a.denominator);return c;}/*** a-b* @param a* @param b* @return 返回a-b的結果*/public static Fraction minus(Fraction a,Fraction b) {a.changeToImproperFraction();b.changeToImproperFraction();if(a.denominator!=b.denominator){int lcm=lCommonMultiple(a.denominator,b.denominator);a.commonReduction(lcm);b.commonReduction(lcm);}Fraction c=new Fraction(0,a.molecular-b.molecular,a.denominator);return c;}/*** a×b* @param a* @param b* @return 返回a×b的結果*/public static Fraction multiply(Fraction a,Fraction b) {a.changeToImproperFraction();b.changeToImproperFraction();Fraction c=new Fraction(0,a.molecular*b.molecular,a.denominator*b.denominator);return c;}/*** a÷b* @param a* @param b* @return 返回a÷b的結果*/public static Fraction divice(Fraction a,Fraction b) {a.changeToImproperFraction();b.changeToImproperFraction();Fraction c=new Fraction(0,a.molecular*b.denominator,a.denominator*b.molecular);return c;}/*** 判斷a、b是否相等,用于判斷題目是否重復* @param a* @param b* @return a=b返回true,否則返回false*/public static boolean isEqual(Fraction a,Fraction b) {a.changeToImproperFraction();b.changeToImproperFraction();if(a.denominator!=b.denominator){int lcm=lCommonMultiple(a.denominator,b.denominator);a.commonReduction(lcm);b.commonReduction(lcm);}if(a.n==b.n&&a.molecular==b.molecular&&a.denominator==b.denominator) return true;return false;}/*** 判斷運算符a、b是否相同,用于判斷題目是否重復* @param a* @param b* @return a、b相同返回true,否則返回false*/public static boolean isEqual(Operator a,Operator b) {if(a.operator==b.operator) return true;return false;} }
?
- Main主函數
public class Main {public static void main(String[] args) {try {boolean flag=true;for(String s:args){if(s.equals("-n")||s.equals("-r")) break; //生成題目和答案文檔else{ //對錯題數量統計flag=false;break;}}//生成題目和答案文檔if(flag){List<Question> questionBase=new ArrayList<Question>(); //題庫int n=1; //題目個數(默認為1)int r=-1; //數值范圍for(int i=0;i<args.length;i++){if(args[i].equals("-n")) n=Integer.valueOf(args[i+1]);if(args[i].equals("-r")) r=Integer.valueOf(args[i+1]);}//沒有給出數值范圍if(r==-1){System.err.println("Warning: The scope of value has not been given!");return;}Question q=new Question(r);questionBase.add(q);while(questionBase.size()<n||Question.isRepeated(questionBase)){q=new Question(r);questionBase.add(q);}//生成題目文件Questions.txtBufferedWriter bw1=new BufferedWriter(new FileWriter("C:\\Users\\asus\\Desktop\\四則運算\\Questions.txt"));for(int i=0;i<questionBase.size();i++){String s=i+1+". "+questionBase.get(i)+"\r\n";bw1.write(s);}bw1.flush();bw1.close();//生成答案文件Answers.txtBufferedWriter bw2=new BufferedWriter(new FileWriter("C:\\Users\\asus\\Desktop\\四則運算\\Answers.txt"));for(int i=0;i<questionBase.size();i++){String s=i+1+". "+questionBase.get(i).result+"\r\n";bw2.write(s);}bw2.flush();bw2.close();//生成答題文件Exercises.txtBufferedWriter bw3=new BufferedWriter(new FileWriter("C:\\Users\\asus\\Desktop\\四則運算\\Exercises.txt"));for(int i=0;i<questionBase.size();i++){String s=i+1+". \r\n";bw3.write(s);}bw3.flush();bw3.close();return;}//對錯題數量統計String e="",a="";for(int i=0;i<args.length;i++){if(args[i].equals("-e")) e=args[i+1];if(args[i].equals("-a")) a=args[i+1];}//沒有給出答題文件路徑if(e.equals("")){System.err.println("404: The exercises file is not found!");return;}//沒有給出答案文件路徑if(a.equals("")){System.err.println("404: The answers file is not found!");return;}List<String> exercises=new ArrayList<String>();List<String> answers=new ArrayList<String>();String str="";//讀取答題文件BufferedReader br1=new BufferedReader(new FileReader(e));for(int i=1;null!=(str=br1.readLine());i++)exercises.add(str.replace(i+". ",""));br1.close();//讀取答案文件BufferedReader br2=new BufferedReader(new FileReader(a));for(int i=1;null!=(str=br2.readLine());i++)answers.add(str.replace(i+". ",""));br2.close();String correct="",wrong="";List<Integer> corrects=new ArrayList<Integer>();List<Integer> wrongs=new ArrayList<Integer>();for(int i=0;i<answers.size();i++){if(exercises.get(i).equals(answers.get(i))) corrects.add(i+1);else wrongs.add(i+1);}for(int i=0;i<corrects.size();i++) correct+=corrects.get(i)+",";for(int i=0;i<wrongs.size();i++) wrong+=wrongs.get(i)+",";if(corrects.size()>0) correct=correct.substring(0,correct.length()-1);if(wrongs.size()>0) wrong=wrong.substring(0,wrong.length()-1);//生成分數文件Grade.txtBufferedWriter bw=new BufferedWriter(new FileWriter("C:\\Users\\asus\\Desktop\\四則運算\\Grade.txt"));bw.write("Correct:"+corrects.size()+"("+correct+")\r\nWrong:"+wrongs.size()+"("+wrong+")");bw.flush();bw.close();} catch (Exception e) {//-a C:\Users\asus\Desktop\四則運算\Answers.txt -e C:\Users\asus\Desktop\四則運算\Exercises.txt e.printStackTrace();}} }
?
單元測試
- 測試題目重復的問題,例:3+(2+1)和1+2+3這兩個題目是重復的,1+2+3和3+2+1是不重復的。
public class test {public static void main(String[] args) {/** 測試題目重復*/Fraction[] r1 = {new Fraction(1,0,1),new Fraction(2,0,1),new Fraction(3,0,1)};Operator[] n1 = {new Operator('+',1),new Operator('+',2)};//1+2+3Fraction[] r2 = {new Fraction(3,0,1),new Fraction(2,0,1),new Fraction(1,0,1)};Operator[] n2 = {new Operator('+',2),new Operator('+',1)};//3+(2+1)Fraction[] r3 = {new Fraction(3,0,1),new Fraction(2,0,1),new Fraction(1,0,1)};Operator[] n3 = {new Operator('+',1),new Operator('+',2)};//3+2+1Fraction[] r4 = {new Fraction(1,0,1),new Fraction(2,0,1),new Fraction(3,0,1)};Operator[] n4 = {new Operator('+',1),new Operator('+',2)};//1+2+3 Question q1=new Question(r1,n1);Question q2=new Question(r2,n2);Question q3=new Question(r3,n3);Question q4=new Question(r4,n4);List<Question> questionBase1=new ArrayList<Question>();questionBase1.add(q1);questionBase1.add(q2);List<Question> questionBase2=new ArrayList<Question>();questionBase2.add(q3);questionBase2.add(q4);for (Question question : questionBase1) {System.out.println(question);} boolean flag1 = Question.isRepeated(questionBase1); System.out.println(flag1);for (Question question : questionBase2) {System.out.println(question);} boolean flag2 = Question.isRepeated(questionBase2); System.out.println(flag2);}}
- 運行結果
? ? ? ? ??
?
?
測試結果
正常測試:
- 首先四則運算的文件夾是空的
? ? ? ?
- 輸入-n 10 -r 10
- 文件夾內生成了如下三個TXT文件
? ? ?
? ? ?? 這是題目的答案
? ? ?? ?這是練習時的答題卡
? ? ?? ?這里是生成的10道題目
- 填入以下答案(3和9故意填錯的),保存
? ? ?
- 輸入?-e C:\Users\asus\Desktop\四則運算\Exercises.txt -a C:\Users\asus\Desktop\四則運算\Answers.txt
???這里生成了一個Grade文件
- 對結果的判斷如下:
? ? ?
?
轉載于:https://www.cnblogs.com/ac666/p/9695396.html
總結
以上是生活随笔為你收集整理的Arithmetic的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微信小程序学习Course 8 本地缓存
- 下一篇: 《感鹤》第七句是什么