结对伙伴:陈振华
项目要求
1.题目:实现一个自动生成小学四则运算题目的命令行程序。
2.需求:
1. 使用 -n 参数控制生成题目的个数
2. 使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围。该参数可以设置为1或其他自然数。该参数必须给定,否则程序报错并给出帮助信息。
3. 生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1 − e2的子表达式,那么e1 ≥ e2。
4. 生成的题目中如果存在形如e1 ÷ e2的子表达式,那么其结果应是真分数。
5. 每道题目中出现的运算符个数不超过3个。
6. 程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。例如,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文件
7. 在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件
8. 程序应能支持一万道题目的生成。
9. 程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计
Github项目地址:https://github.com/kvhong/Myapp
设计
生成表达式:
1.通过命令行输入题目数量决定for循环的循环次数;输入数值大小决定表达式中整数、真分数分母分子的大小。
2.调用FormExpression函数生成表达式并输出:用两个数组分别存储随机得到的整数分数和运算符。如果表达式中有除运算符,将数值数组中的整数分数从1开始生成,以解决出现除号右边结果为0的情况;如果表达式中有减号,则比较减号两边结果大小,在需要时进行交换,以解决生成负数结果。
计算结果:
1.先将上面生成的表达式(以字符串存储)处理为列表形式,再将此列表中的中缀表达式变换为后缀表达式,再用后缀表达式计算最终结果。
2.计算过程:遍历后缀表达式列表,如果是数值存入栈中,如果是运算符则弹出栈中元素进行运算,再压入栈中。
生成文件:
1.将生成的表达式和结果分别用FileOutputStream存入Exercises.txt和Answers.txt文件中
比较对错:
1.用命令行输入试卷文件和答案文件,分别用InputStreamReader和BufferReader读取内容。
2.取一行存入数组,取数组最后一个元素,加入数组中,然后对两个数组进行对比,以得到正确率。
未完善问题
1.处理减号时,仍会出现极少数负数问题,需重新构思实现。
2.未实现括号,由于先实现了表达式的输出,未即使同时实现括号的加入,现比较难加入。
代码
Fenshu:定义分数结构,并实现分数的加减乘除,其中包括了对两部分表达式的大小比较函数compute
FormExpression:生成表达式
import java.util.Random; public class FormExpression { String FormExpression(int size , int operatenum) { Random r = new Random(); String[] operate = {"+","-","×","÷"}; StringBuffer str = new StringBuffer(); StringBuffer strnew = new StringBuffer(); int num; int numerator; int denominator; String oper; Fenshu fenshu; int divcount = 0; String[] numlist = new String[operatenum+1]; String[] operlist = new String[operatenum]; for(int i=0;i) { oper=operate[r.nextInt(4)]; operlist[i]=oper; if(oper.equals("÷")) { divcount++; } } for(int i=0;i ) { if(divcount==0) { num=r.nextInt(size); numerator = r.nextInt(size); denominator = r.nextInt(size); }else { num=r.nextInt(size)+1; numerator = r.nextInt(size)+1; denominator = r.nextInt(size)+1; } if(denominator!=0&&numerator<=denominator) { fenshu = new Fenshu(numerator,denominator); }else { fenshu = new Fenshu(denominator, numerator); } numlist[i]=randominput(num, fenshu); } for(int i=0;i<2*operatenum+1;i++) { if(i%2==0) { str.append(numlist[i/2]+" "); } if(i%2!=0) { str.append(operlist[(i-1)/2]+" "); } } str.append("="+" "); String[] judge = str.toString().split(" "); for(int i=1;i ) { if(judge[i].equals("-")) { Fenshu fs = new Fenshu(); Expression ex = new Expression(); StringBuffer font = new StringBuffer(); StringBuffer back = new StringBuffer(); StringBuffer newstr = new StringBuffer(); for(int k=0;k) { font.append(judge[k]+" "); } for(int k=i+1;k ) { back.append(judge[k]+" "); } Fenshu fontfs = ex.count(font.toString()); Fenshu backfs = ex.count(back.toString()); String fontstr = fontfs.numerator+"/"+fontfs.denominator; String backstr = backfs.numerator+"/"+backfs.denominator; if(fs.compute(fontstr, backstr)) { newstr.append(back.toString()+"- "+font.toString()+"= "); String[] newjudge = newstr.toString().split(" "); for(int k=0;k ) { judge[k]=newjudge[k]; } } } } for(int i=0;i ) { strnew.append(judge[i]+" "); } return strnew.toString(); } String randominput(int num,Fenshu fenshu) { String numstr = num+""; String fenshustr = fenshu.getNumerator() +"/"+fenshu.getDenominator(); String[] strlist = {numstr , fenshustr}; Random r = new Random(); return strlist[r.nextInt(2)].toString(); } }
Expression:计算结果
import java.util.*; public class Expression { public char[] op = {'+','-','×','÷','(',')'}; public String[] strOp = {"+","-","×","÷","(",")"}; public boolean isDigit(char c){ if(c>='0'&&c<='9'){ return true; } return false; } public boolean isOp(char c){ for(int i=0;i){ if(op[i]==c){ return true; } } return false; } public boolean isOp(String s){ for(int i=0;i ){ if(strOp[i].equals(s)){ return true; } } return false; } public boolean isFenshu(char c) { if(c=='/') { return true; } return false; } //处理输入的计算式 public List process(String str){ List list = new ArrayList (); char c; StringBuilder sb = new StringBuilder(); for(int i=0;i ){ c = str.charAt(i); if(isDigit(c)||isFenshu(c)){ sb.append(c); } if(isOp(c)){ if(sb.toString().length()>0){ list.add(sb.toString()); sb.delete(0, sb.toString().length()); } list.add(c+""); } } if(sb.toString().length()>0){ list.add(sb.toString()); sb.delete(0, sb.toString().length()); } return list; } public void printList(List list){ for(String o:list){ System.out.print(o+" "); } } //一般计算式转换为后缀表达式 public List simpleTosuffix(List list){ List Postfixlist = new ArrayList ();//存放后缀表达式 Stack stack = new Stack ();//暂存操作符 for(int i=0;i ){ String s = list.get(i); if(s.equals("(")){ stack.push(s); }else if(s.equals("×")||s.equals("÷")){ stack.push(s); }else if(s.equals("+")||s.equals("-")){ if(!stack.empty()){ while(!(stack.peek().equals("("))){ Postfixlist.add(stack.pop()); if(stack.empty()){ break; } } stack.push(s); }else{ stack.push(s); } }else if(s.equals(")")){ while(!(stack.peek().equals("("))){ Postfixlist.add(stack.pop()); } stack.pop(); }else{ Postfixlist.add(s); } if(i==list.size()-1){ while(!stack.empty()){ Postfixlist.add(stack.pop()); } } } return Postfixlist; } //后缀表达式计算 public Fenshu count(String str){ List list2 = process(str); List list = simpleTosuffix(list2); Stack stack = new Stack (); for(int i=0;i ){ String s = list.get(i); if(!isOp(s)){ Fenshu fenshu; StringTokenizer tokenizer = new StringTokenizer(s, "/"); int numerator = Integer.parseInt(tokenizer.nextToken()); if(tokenizer.hasMoreTokens()) { int denominator = Integer.parseInt(tokenizer.nextToken()); fenshu = new Fenshu(numerator, denominator); }else { fenshu = new Fenshu(numerator, -1); } stack.push(fenshu); }else{ if(s.equals("+")){ Fenshu a1 = stack.pop(); Fenshu a2 = stack.pop(); Fenshu v = a2.add(a1); stack.push(v); }else if(s.equals("-")){ Fenshu a1 = stack.pop(); Fenshu a2 = stack.pop(); Fenshu v = a2.sub(a1); stack.push(v); }else if(s.equals("×")){ Fenshu a1 = stack.pop(); Fenshu a2 = stack.pop(); Fenshu v = a2.muti(a1); stack.push(v); }else if(s.equals("÷")){ Fenshu a1 = stack.pop(); Fenshu a2 = stack.pop(); Fenshu v = a2.div(a1); stack.push(v); } } } return stack.pop(); } }
build:实现命令行题目数量和数值大小输入和输出表达式和答案到TXT文件
import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.Random; import java.util.Scanner; public class build { @SuppressWarnings("resource") build() throws IOException { Scanner scannernum; Scanner scannersize; int num=0; int size=0; FormExpression fe = new FormExpression(); Expression ex = new Expression(); Random r = new Random(); File que = new File("Exercises.txt"); File ans = new File("Answers.txt"); if(!que.exists()) { que.createNewFile(); } if(!ans.exists()) { ans.createNewFile(); } FileOutputStream fosque = new FileOutputStream(que); FileOutputStream fosans = new FileOutputStream(ans); System.out.println("请输入要生成的题目数(命令形式为:-n 自然数):"); scannernum = new Scanner(System.in); String[] strnum = scannernum.nextLine().split(" "); if(strnum[0].equals("-n")&&strnum.length==2) { if(Integer.parseInt(strnum[1])>=1) { num = Integer.parseInt(strnum[1]); System.out.println("请输入题目中数值的最大值(命令形式为:-r 自然数):"); scannersize = new Scanner(System.in); String[] strsize = scannersize.nextLine().split(" "); if(strsize[0].equals("-r")&&strsize.length==2) { if(Integer.parseInt(strsize[1])>=2) { size = Integer.parseInt(strsize[1]); for(int i=0;i) { int opernum = r.nextInt(3)+1; String res = fe.FormExpression(size, opernum); String result = i+1 + "." + res +"\r\n"; byte[] print = result.getBytes(); fosque.write(print); Fenshu ansresult = ex.count(res); String ansres = null; if(ansresult.getDenominator()==1) { ansres = i+1 + ". " +ansresult.getNumerator() + "\r\n"; }else { if(ansresult.getNumerator()>ansresult.getDenominator()) { int fz = ansresult.getNumerator(); int fm = ansresult.getDenominator(); int mut = fz / fm; int newfz = fz % fm; ansres = i+1 + ". " + mut + "'" +newfz+"/"+fm + "\r\n"; }else if(ansresult.getNumerator()==ansresult.getDenominator()) { ansres = i+1 + ". " + "1" + "\r\n"; }else { ansres = i+1 + ". " +ansresult.getNumerator()+"/"+ansresult.getDenominator() + "\r\n"; } } byte[] ansprint = ansres.getBytes(); fosans.write(ansprint); System.out.println(res); } fosque.flush(); fosans.flush(); fosque.close(); fosans.close(); }else { System.out.println("-r参数设置错误,请重新输入,-n参数必须大于等于2!"); } }else { System.out.println("-r命令输入错误,请重新输入!"); } }else { System.out.println("-n参数设置错误,请重新输入,-n参数必须大于等于1!"); } }else { System.out.println("-n命令输入错误,请重新输入!"); } } }
CorrectandWrong:命令行输入试卷文件和答案文件,判断对错并统计
import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.text.NumberFormat; import java.util.Scanner; public class CorrectandWrong { private Scanner scanner1; private Scanner scanner2; CorrectandWrong() throws IOException { System.out.println("请输入试题文件名: "); scanner1 = new Scanner(System.in); String Exefile = scanner1.nextLine(); System.out.println("请输入答案文件名: "); scanner2 = new Scanner(System.in); String Ansfile = scanner2.nextLine(); File exefile = new File(Exefile); File ansfile = new File(Ansfile); if(exefile.exists()&&ansfile.exists()) { InputStreamReader exread = new InputStreamReader(new FileInputStream(Exefile), "GB2312"); BufferedReader exbr = new BufferedReader(exread); InputStreamReader anread = new InputStreamReader(new FileInputStream(Ansfile), "GB2312"); BufferedReader anbr = new BufferedReader(anread); String ex; String an; String exback = null; String anback = null; String[] exlist = null; String[] anlist = null; String[] Exercises = null; StringBuffer exercises = new StringBuffer(); String[] Answers = null; StringBuffer answers = new StringBuffer(); int correctnum = 0; int wrongnum = 0; StringBuffer correct = new StringBuffer(); StringBuffer wrong = new StringBuffer(); while((ex=exbr.readLine())!=null) { exlist = ex.split(" "); if(exlist[exlist.length-1].equals("=")) { exback = "-"; }else { exback = exlist[exlist.length-1]; } exercises.append(exback+","); } Exercises = exercises.toString().split(","); while((an=anbr.readLine())!=null) { anlist = an.split(" "); anback = anlist[anlist.length-1]; answers.append(anback+","); } Answers = answers.toString().split(","); for(int i=0;i) { if(Exercises[i].equals(Answers[i])) { correct.append(i+1+" "); correctnum++; }else { wrong.append(i+1+" "); wrongnum++; } } System.out.println("Correct: "+correctnum+" ( "+correct+")"); System.out.println("Wrong: "+wrongnum+" ( "+wrong+")"); NumberFormat nt = NumberFormat.getPercentInstance(); nt.setMinimumFractionDigits(2); double correctpercent = (double) correctnum / (double) Exercises.length; double wrongpercent = (double) wrongnum / (double) Exercises.length; System.out.println("正确率: "+nt.format(correctpercent)); System.out.println("错误率: "+nt.format(wrongpercent)); exread.close(); anread.close(); exbr.close(); anbr.close(); }else { System.out.println("找不到指定文件!"); } } }
Myapp:主函数只用while持续调用build类和CorrectandWrong类,此处不展示。
测试
表达式输出
以50条表达式,数值小于10为例(可实现10000条表达式):
1.2/9 × 6 × 8 - 2/9 =
2.2 - 1 ÷ 1 - 1/5 =
3.2 × 5 + 0 =
4.9 - 6 - 1/9 =
5.1/1 ÷ 2 =
6.2 - 3/8 - 1/6 × 0/1 =
7.7 - 1/2 =
8.9 ÷ 2/3 =
9.3/7 × 3 - 7 × 0/1 =
10.1/4 + 3/7 × 7 - 2/7 =
11.1 - 0/1 =
12.2 - 1/8 + 5/6 =
13.1/2 - 2/7 - 0/1 × 1/4 =
14.0 + 5 + 1/1 =
15.5 + 0 =
16.9 - 5 =
17.1/2 + 1/3 + 2 ÷ 3/5 =
18.2/3 ÷ 3 =
19.3 - 3/4 - 5/9 =
20.5 + 0/1 × 8 =
21.1/1 ÷ 1/2 + 10 ÷ 7 =
22.7 × 1/1 + 2/7 - 1 =
23.5 × 8 - 8 =
24.1/2 + 7 × 1/9 × 5 =
25.1/1 ÷ 5 + 1 =
26.6 - 5 =
27.8 ÷ 5/9 ÷ 2/3 =
28.3 - 0/1 =
29.2 + 1 - 8/9 =
30.3/7 ÷ 3 × 1 ÷ 1 =
31.6 × 2/3 =
32.4 + 2 ÷ 5 =
33.2/3 × 2 ÷ 3 =
34.6 - 1/8 + 3/4 ÷ 1/5 =
35.7 - 4/7 + 0 =
36.4 ÷ 10 ÷ 2/9 =
37.1/2 × 3 - 1/1 =
38.6 ÷ 1/7 =
39.1 ÷ 3 × 5 + 6 =
40.0 + 3 × 6/7 =
41.2/3 + 7 =
42.3/5 - 9 × 0 + 1/3 =
43.2/9 ÷ 1/2 + 1/7 ÷ 4 =
44.2 - 5/7 =
45.1 + 1/4 ÷ 9 + 1/9 =
46.5 ÷ 1/2 - 4 =
47.2 ÷ 4 - 4/9 =
48.1/1 - 2/9 × 3/8 =
49.1/6 - 0 - 1/9 =
50.1/1 + 0/1 =
答案:
1. 10'4/9
2. 4/5
3. 10
4. 2'8/9
5. 1/2
6. 1'5/8
7. 6'1/2
8. 13'1/2
9. 1'2/7
10. 2'27/28
11. 1
12. 2'17/24
13. 3/14
14. 6
15. 5
16. 4
17. 4'1/6
18. 2/9
19. 1'25/36
20. 5
21. 3'3/7
22. 6'2/7
23. 32
24. 4'7/18
25. 1'1/5
26. 1
27. 9'3/5
28. 3
29. 2'1/9
30. 1/7
31. 4
32. 4'2/5
33. 4/9
34. 9'5/8
35. 6'3/7
36. 4/45
37. 1/2
38. 42
39. 6'1/15
40. 2'4/7
41. 7'2/3
42. 14/15
43. 121/252
44. 1'2/7
45. 1'5/36
46. 6
47. 1/18
48. 11/12
49. 1/18
50. 1
判断对错并统计:以上面50条为例,将其中10条填入正确答案,其他为空,随机分布在50条表达式中:
PSP
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 120 | 210 |
· Estimate | · 估计这个任务需要多少时间 | 120 | 210 |
Development | 开发 | 3170 | 4420 |
· Analysis | · 需求分析 (包括学习新技术) | 90 | 180 |
· Design Spec | · 生成设计文档 | 120 | 200 |
· Design Review | · 设计复审 (和同事审核设计文档) | 80 | 120 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 40 | 60 |
· Design | · 具体设计 | 120 | 200 |
· Coding | · 具体编码 | 2400 | 3000 |
· Code Review | · 代码复审 | 120 | 300 |
· Test | · 测试(自我测试,修改代码,提交修改) | 200 | 360 |
Reporting | 报告 | 190 | 220 |
· Test Report | · 测试报告 | 100 | 120 |
· Size Measurement | · 计算工作量 | 30 | 20 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 | 80 |
合计 | 3480 | 4850 |
总结
在这次的结对编程中,真正体会到了两个人不同思想的碰撞,虽然你一开始想出来的方法可行,但是别人想出来的可能更加的简便易实现。所以多跟他人交流沟通会有很大的收获。在这过程中,我和结对伙伴就生成表达式过程中该如何存储,如果结果为负数在生成过程中该如何实现不生成负数结果的表达式,分数的实现和计算,括号的优先运算,后缀表达式的实现进行了较深入的探讨。陈振华同学也针对我的编码风格和代码简约程度提出了批评意见,我也认识到这一点,需认真学习代码编写的简约清晰化,在后面的代码编写中更加注意这些问题。