Arithmetic
Posted ac666
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Arithmetic相关的知识,希望对你有一定的参考价值。
前言
合作伙伴:林奇凯
项目简介
- 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; } @Override public 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=‘+‘; //43 break; case 1: operator=‘-‘; //45 break; case 2: operator=‘ב; //215 break; 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; } @Override public 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 ‘-‘: //如果减数大于被减数,则返回-1 if(!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,则返回-1 if(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; } @Override public 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.txt BufferedWriter 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)+" "; bw1.write(s); } bw1.flush(); bw1.close(); //生成答案文件Answers.txt BufferedWriter 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+" "; bw2.write(s); } bw2.flush(); bw2.close(); //生成答题文件Exercises.txt BufferedWriter bw3=new BufferedWriter(new FileWriter("C:\\Users\\asus\\Desktop\\四则运算\\Exercises.txt")); for(int i=0;i<questionBase.size();i++){ String s=i+1+". "; 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.txt BufferedWriter bw=new BufferedWriter(new FileWriter("C:\\Users\\asus\\Desktop\\四则运算\\Grade.txt")); bw.write("Correct:"+corrects.size()+"("+correct+") Wrong:"+wrongs.size()+"("+wrong+")"); bw.flush(); bw.close(); } catch (Exception e) { //-a C:UsersasusDesktop四则运算Answers.txt -e C:UsersasusDesktop四则运算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+3 Fraction[] 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+1 Fraction[] 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:UsersasusDesktop四则运算Exercises.txt -a C:UsersasusDesktop四则运算Answers.txt
这里生成了一个Grade文件
- 对结果的判断如下:
以上是关于Arithmetic的主要内容,如果未能解决你的问题,请参考以下文章
1088. Rational Arithmetic (20)——PAT (Advanced Level) Practise
CF 1114 E. Arithmetic Progression
CodeForces 625D Finals in arithmetic