结对伙伴:陈振华
项目要求
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<operatenum;i++) {
oper=operate[r.nextInt(4)];
operlist[i]=oper;
if(oper.equals("÷")) {
divcount++;
}
} for(int i=0;i<operatenum+1;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<judge.length-1;i+=2) {
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<i;k++) {
font.append(judge[k]+" ");
}
for(int k=i+1;k<judge.length-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<newjudge.length;k++) {
judge[k]=newjudge[k];
}
}
}
}
for(int i=0;i<judge.length;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<op.length;i++){
if(op[i]==c){
return true;
}
}
return false;
}
public boolean isOp(String s){
for(int i=0;i<strOp.length;i++){
if(strOp[i].equals(s)){
return true;
}
}
return false;
}
public boolean isFenshu(char c) {
if(c=='/') {
return true;
}
return false;
} //处理输入的计算式
public List<String> process(String str){
List<String> list = new ArrayList<String>();
char c;
StringBuilder sb = new StringBuilder();
for(int i=0;i<str.length();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<String> list){
for(String o:list){
System.out.print(o+" ");
}
} //一般计算式转换为后缀表达式
public List<String> simpleTosuffix(List<String> list){
List<String> Postfixlist = new ArrayList<String>();//存放后缀表达式
Stack<String> stack = new Stack<String>();//暂存操作符
for(int i=0;i<list.size();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<String> list2 = process(str);
List<String> list = simpleTosuffix(list2);
Stack<Fenshu> stack = new Stack<Fenshu>();
for(int i=0;i<list.size();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<num;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<Exercises.length;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 |
总结
在这次的结对编程中,真正体会到了两个人不同思想的碰撞,虽然你一开始想出来的方法可行,但是别人想出来的可能更加的简便易实现。所以多跟他人交流沟通会有很大的收获。在这过程中,我和结对伙伴就生成表达式过程中该如何存储,如果结果为负数在生成过程中该如何实现不生成负数结果的表达式,分数的实现和计算,括号的优先运算,后缀表达式的实现进行了较深入的探讨。陈振华同学也针对我的编码风格和代码简约程度提出了批评意见,我也认识到这一点,需认真学习代码编写的简约清晰化,在后面的代码编写中更加注意这些问题。