结对编程--四则运算(Java)萧英杰 夏浚杰
Github项目地址
功能要求
题目:实现一个自动生成小学四则运算题目的命令行程序
- 使用 -n 参数控制生成题目的个数(实现)
- 使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围,该参数可以设置为1或其他自然数。该参数必须给定,否则程序报错并给出帮助信息(实现)
- 生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如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文件 (实现)
- 在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件 (实现)
- 程序应能支持一万道题目的生成 (实现)
- 程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,统计结果输出到文件Grade.txt (实现)
PSP
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | 90 |
· Estimate | · 估计这个任务需要多少时间 | 60 | 90 |
Development | 开发 | 1920 | 2270 |
· Analysis | · 需求分析 (包括学习新技术) | 200 | 250 |
· Design Spec | · 生成设计文档 | 50 | 60 |
· Design Review | · 设计复审 (和同事审核设计文档) | 20 | 20 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
· Design | · 具体设计 | 30 | 40 |
· Coding | · 具体编码 | 1500 | 1800 |
· Code Review | · 代码复审 | 50 | 30 |
· Test | · 测试(自我测试,修改代码,提交修改) | 60 | 60 |
Reporting | 报告 | 100 | 100 |
· Test Report | · 测试报告 | 60 | 60 |
· Size Measurement | · 计算工作量 | 10 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 30 |
合计 | 2080 | 2460 |
效能分析
编码过程中,在表达式生成和查重方面花费了很多时间,对于查重,需要将已经生成的表达式加入到一个集合里,原本想用数组存放查重表达式,后来发现此方案效率偏低,之后在网上查找了一些资料后,采用HashMap存放,使用containsKey()进行查重。经过测试,该方案优于原方案。此外,还对代码进行了一定程度上的优化,使代码更加简洁,效率提高。
设计实现过程
1.操作数的生成
题目要求生成的操作数可以是自然数、分数,由于整数可以看成分母为1的分数,所以将整数化作分数进行计算。因此设计了一个分数类用于分数的定义,存放,计算和输出。
2.表达式的生成和计算
经过查询资料和讨论之后,我们决定用fraction_create方法和operator_create方法随机生成几个操作数和运算符,将它们按规则组成表达式。至于表达式的计算,生成的表达式是中缀表达式,我们将它转换为后缀表达式,并calculate方法计算,得出答案。
代码说明
Fraction类:分数的的定义,存放,计算和输出
package myapp;
public class Fraction {
//分子
private int a;
//分母
private int b;
//分数
public Fraction(int a, int b) {
this.a = a;
if (b == 0) {
throw new ArithmeticException("分母不能为零");
} else {
this.b = b;
}
simple();
}
//化简
private Fraction simple() {
int gcd = this.gcd(this.a, this.b);
this.a /= gcd;
this.b /= gcd;
return this;
}
//最大公约数
private int gcd(int a, int b) {
int mod = a % b;
if (mod == 0) {
return b;
} else {
return gcd(b, mod);
}
}
//四则运算
public Fraction add(Fraction second) {//加法
return new Fraction(this.a * second.b + second.a * this.b,
this.b * second.b);
}
public Fraction sub(Fraction second) {//减法
return new Fraction(this.a * second.b - second.a * this.b,
this.b * second.b);
}
public Fraction mult(Fraction second) {//乘法
return new Fraction(this.a*second.a,
this.b * second.b);
}
public Fraction div(Fraction second) {//除法
return new Fraction(this.a*second.b,
this.b * second.a);
}
//分数类转字符串类型
public String toString() {
if (this.b==1) {
return String.valueOf(a);
}
else if(this.a>this.b) {
int c=0;
c=this.a/this.b;
this.a=this.a%this.b;
return String.valueOf(c)+"'"+String.valueOf(a)+"/"+String.valueOf(b);
}
else{
return String.valueOf(a)+"/"+String.valueOf(b);}
}
public static Fraction tofraction(String str) {
str=str.replaceAll(" ", "");
int a=1;
int b=1;
int s1=str.indexOf("'");
int s2=str.indexOf("/");
if(s1!=-1) {
int c=Integer.valueOf(str.substring(0,s1));
b=Integer.valueOf(str.substring(s2+1));
a=c*b+Integer.valueOf(str.substring(s1+1,s2));
} else if(s2!=-1) {
b=Integer.valueOf(str.substring(s2+1));
a=Integer.valueOf(str.substring(0,s2));
} else {
a=Integer.valueOf(str);
b=1;
}
return new Fraction(a,b);
}
//比较大小,比较此分数是否大于输入分数
public boolean isgreaterthan2(Fraction f) {
int z=this.a * f.b - f.a * this.b;
if(z>0) {return true;}
else return false;
}
public boolean isZero() {
return a == 0;
}
}
Expression类:表达式的生成
package myapp;
import java.util.Random;
public class Expression {
Random random=new Random();
Calc c=new Calc();
//随机生成一个分数
public Fraction fraction_create(int r) {
int choose=random.nextInt(2)+1;
int denominator=1;
int numerator=0;
if(choose==1) { //整数
numerator=random.nextInt(r);
denominator=1;
}else {
denominator=random.nextInt(r)+1;
numerator=random.nextInt(r*r+1);
while(numerator/denominator>=r) {
denominator=random.nextInt(r)+1;
numerator=random.nextInt(r+1);}
}
return new Fraction( numerator , denominator );
}
//随机生成运算符
public char operator_create() {
int oper=random.nextInt(4);
char sign;
switch (oper) {
case 0:
sign='+';
break;
case 1:
sign='-';
break;
case 2:
sign='×';
break;
case 3:
sign='÷';
break;
default:
sign='+';
}
return sign;
}
//生成表达式
public String getexp(int r) {
String expression="";
int ran=random.nextInt(3);
switch (ran) {
case 0:
expression=oneopexp(r);
break;
case 1:
expression=twoopexp(r);
break;
case 2:
expression=threeopexp(r);
break;
}
return expression;
}
//一个运算符
public String oneopexp(int r) {
Fraction f1=fraction_create(r);
Fraction f2=fraction_create(r);
char op=operator_create();
String exp ="";
switch (op) {
case '+':
exp= f1+" + "+f2;
break;
case '-':
if(!f1.isgreaterthan2(f2)) {
Fraction temp;
temp=f1;f1=f2;f2=temp;
}
exp= f1+" - "+f2;
break;
case '×':
exp= f1+" × "+f2;
break;
case '÷':
while(f2.isZero()) {
f2=fraction_create(r);
}
exp= f1+" ÷ "+f2;
break;
}
return exp;
}
//两个运算符
public String twoopexp(int r){
Fraction f1=fraction_create(r);
Fraction f2=fraction_create(r);
Fraction f3=fraction_create(r);
char op1=operator_create();
char op2=operator_create();
String exp ="";
String exp1="";
switch (op1) {
case '+':
exp= f1+" + "+f2;
break;
case '-':
if(!f1.isgreaterthan2(f2)) {
Fraction temp;
temp=f1;f1=f2;f2=temp;
}
exp= f1+" - "+f2;
break;
case '×':
exp= f1+" × "+f2;
break;
case '÷':
while(f2.isZero()) {
f2=fraction_create(r);
}
exp= f1+" ÷ "+f2;
break;
}
switch (op2) {
case '+':
exp1=exp+" + "+f3;
break;
case '-':
if(!c.calculate(exp).isgreaterthan2(f3)) {
exp1= f3+" - "+"("+exp+")";
}else {
exp1= exp+" - "+f3;}
break;
case '×':
exp1= "("+exp+")"+" × "+f3;
break;
case '÷':
while(f3.isZero()) {
f3=fraction_create(r);
}
exp1="("+exp+")"+" ÷ "+f3;
break;
}
return exp1;
}
//三个运算符改
public String threeopexp(int r){
Fraction f1=fraction_create(r);
Fraction f2=fraction_create(r);
Fraction f3=fraction_create(r);
Fraction f4=fraction_create(r);
char op1=operator_create();
char op2=operator_create();
char op3=operator_create();
String exp ="";
String exp1="";
String exp2="";
switch (op1) {
case '+':
exp= f1+" + "+f2;
break;
case '-':
if(!f1.isgreaterthan2(f2)) {
Fraction temp;
temp=f1;f1=f2;f2=temp;
}
exp= f1+" - "+f2;
break;
case '×':
exp= f1+" × "+f2;
break;
case '÷':
while(f2.isZero()) {
f2=fraction_create(r);
}
exp= f1+" ÷ "+f2;
break;
}
switch (op2) {
case '+':
exp1=exp+" + "+f3;
break;
case '-':
if(!c.calculate(exp).isgreaterthan2(f3)) {
exp1= f3+" - "+"("+exp+")";
}else {
exp1= exp+" - "+f3;}
break;
case '×':
exp1= "("+exp+")"+" × "+f3;
break;
case '÷':
while(f3.isZero()) {
f3=fraction_create(r);
}
exp1="("+exp+")"+" ÷ "+f3;
break;
}
switch (op3) {
case '+':
exp2=exp1+" + "+f4;
break;
case '-':
if(!c.calculate(exp1).isgreaterthan2(f4)) {
exp2= f4+" - "+"("+exp1+")";
}else {
exp2= exp1+" - "+f4;}
break;
case '×':
exp2= "("+exp1+")"+" × "+f4;
break;
case '÷':
while(f4.isZero()) {
f4=fraction_create(r);
}
exp2="("+exp1+")"+" ÷ "+f4;
break;
}
return exp2;
}
}
Calc类:中缀表达式转后缀表达式并计算,查重表达式的生成
package myapp;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class Calc {
char[] oper6= {'+','-','×','÷','(',')'};
String[] oper4={"+","-","×","÷"};
private static int ADDITION=1;
private static int SUBTRACTION=1;
private static int MULTIPLICATION=2;
private static int DIVISION=2;
public static int getValue(String op){
int value;
switch (op){
case "+":
value=ADDITION;
break;
case "-":
value=SUBTRACTION;
break;
case "×":
value=MULTIPLICATION;
break;
case "÷":
value=DIVISION;
break;
default:
value=0;
}
return value;
}
//判断是否是操作符
public boolean isoper(char c) {
for(char op:oper6) {
if(op==c) return true;
}
return false;
}
public boolean isoper(String s) {
for(String op1:oper4) {
if(s.equals(op1)) return true;
}
return false;
}
//判断是否是数字
public boolean isfra(char c) {
if(c>='0'&&c<='9')
{return true;}
return false;
}
public boolean isfraop(char c) {
if(c=='\''||c=='/') {
return true;
}
return false;
}
//将String类型表达式转换成List<String>类型
public List<String> Stringtolist(String str){
List<String> infix = new ArrayList<String>();
str=str.replace(" ", "");
char op;
String temp="";
for(int i=0;i<str.length();i++) {
op=str.charAt(i);
if(isfra(op)||isfraop(op)) {
temp+=op;
} else if(isoper(op)) {
if(!temp.isEmpty()) {
infix.add(temp);
temp="";
}
infix.add(op+"");
}
}
if(!temp.isEmpty()) {
infix.add(temp);
temp="";
}
return infix;
}
//将中缀表达式转换为后缀表达式
public List<String> infixtopostfix(List<String> infix){
List<String> postfix=new ArrayList<String>();
Stack<String> s1=new Stack<String>();
for (String str : infix) {
if(str.equals("(")) {
s1.push(str);
}
else if(str.equals(")")) {
while(!s1.peek().equals("(")) {
postfix.add(s1.pop());
}
s1.pop();
}
else if(str.equals("+")||str.equals("-")||str.equals("×")||str.equals("÷")) {
while (s1.size() != 0 && getValue(s1.peek()) >= getValue(str)) {
postfix.add(s1.pop());
}
s1.push(str);
} else {
postfix.add(str);
}
}
while (s1.size() != 0) {
postfix.add(s1.pop());
}
return postfix;
}
//对后缀表达式进行计算
public Fraction calculate(String str) {
List<String> linfix=Stringtolist(str);
List<String> lpostfix=infixtopostfix(linfix);
Stack<Fraction> s2=new Stack<Fraction>();
Fraction f1;
Fraction f2;
Fraction answer;
for(String cal:lpostfix) {
if(!isoper(cal)) {
Fraction f=Fraction.tofraction(cal);
s2.push(f);
} else {
switch (cal) {
case "+":
f1=s2.pop();
f2=s2.pop();
answer=f2.add(f1);
s2.push(answer);
break;
case "-":
f1=s2.pop();
f2=s2.pop();
answer=f2.sub(f1);
s2.push(answer);
break;
case "×":
f1=s2.pop();
f2=s2.pop();
answer=f2.mult(f1);
s2.push(answer);
break;
case "÷":
f1=s2.pop();
f2=s2.pop();
answer=f2.div(f1);
s2.push(answer);
break;
}
}
}
return s2.pop();
}
//查重表达式的生成
public List<String> getcnki(String str){
List<String> linfix=Stringtolist(str);
List<String> lpostfix=infixtopostfix(linfix);
List<String> cnkiexp=new ArrayList<String>();
Stack<String> st=new Stack<String>();
String top1=null;
String top2=null;
for(String cal:lpostfix) {
if(!isoper(cal)) {
st.push(cal);
} else if(cal.equals("+")||cal.equals("-")||cal.equals("×")) {
cnkiexp.add(cal);
top1=st.pop();
top2=st.pop();
if(top2!="@") {
cnkiexp.add(top2);
}
if(top1!="@") {
cnkiexp.add(top1);
}
st.push("@");
}
else if(cal.equals("÷")) {
cnkiexp.add(cal);
top1=st.pop();
top2=st.pop();
cnkiexp.add(top2);
cnkiexp.add(top1);
st.push("@");
}
}
return cnkiexp;
}
//将List<String>类型表达式转换成String类型
public String list2String(List<String> list) {
String str1="";
for(String s:list) {
str1=str1+s+" ";
}
return str1;
}
}
Create类:题目和答案文档的生成
package myapp;
import java.io.*;
import java.util.HashMap;
import java.util.List;
public class Create {
Expression expression=new Expression();
Calc cal=new Calc();
HashMap<String,String> map = new HashMap<String,String>();
//在当前目录下生成练习题和答案
public void cr(int n,int r) throws IOException {
BufferedWriter btex=new BufferedWriter(new FileWriter(".\\Exercises.txt"));
BufferedWriter btan=new BufferedWriter(new FileWriter(".\\Answers.txt"));
for(int i=1;i<n+1;i++){
String exps=expression.getexp(r);
List<String> cnkiexp1=cal.getcnki(exps);
String cnki1=cal.list2String(cnkiexp1);
boolean contains2 =false;
if(cnkiexp1.get(0).equals("+")||cnkiexp1.get(0).equals("×")) {
List<String> cnkiexp2=cnkiexp1;
String t1=cnkiexp2.get(1);
String t2=cnkiexp2.get(2);
cnkiexp2.set(1, t2);
cnkiexp2.set(2, t1);
String cnki2=cal.list2String(cnkiexp2);
contains2 = map.containsKey(cnki2);
}
if(i==1) {map.put(cnki1,"");}
boolean contains = map.containsKey(cnki1);
if(contains==true||contains2==true) {
exps=expression.getexp(r);
cnkiexp1=cal.getcnki(exps);
cnki1=cal.list2String(cnkiexp1);
contains2 =false;
if(cnkiexp1.get(0).equals("+")||cnkiexp1.get(0).equals("×")) {
List<String> cnkiexp2=cnkiexp1;
String t1=cnkiexp2.get(1);
String t2=cnkiexp2.get(2);
cnkiexp2.set(1, t2);
cnkiexp2.set(2, t1);
String cnki2=cal.list2String(cnkiexp2);
contains2 = map.containsKey(cnki2);
}
}
map.put(cnki1,"");
Fraction answer=cal.calculate(exps);
String answers=answer.toString();
btex.write(i+". "+exps+"\r\n");
btan.write(i+". "+answers+"\r\n");
}
btex.flush();
btan.flush();
btex.close();
btan.close();
}
}
Checkanswer:批改答卷
package myapp;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class Checkanswer {
public void check(String expath,String anpath) throws IOException{
BufferedReader brex=new BufferedReader(new FileReader(expath));
BufferedReader bran=new BufferedReader(new FileReader(anpath));
BufferedWriter bwgrade=new BufferedWriter(new FileWriter(".\\Grade.txt"));
List<String> Correct=new ArrayList<String>();
List<String> Wrong=new ArrayList<String>();
String an=null;
String ex=null;
while((an=bran.readLine())!=null) {
ex=brex.readLine();
int point=an.indexOf(".");
String stran=an.substring(point+1);
stran=stran.trim();
String strex=ex.substring(point+1);
strex=strex.trim();
if(stran.equals(strex)) {
String cno=an.substring(0, point);
Correct.add(cno);
}else {
String wno=an.substring(0, point);
Wrong.add(wno);
}
}
String corr=String.join(",",Correct);
String wr=String.join(",",Wrong);
bwgrade.write("Correct: "+Correct.size()+" ("+corr+")"+"\r\n");
bwgrade.write("Wrong: "+Wrong.size()+" ("+wr+")");
bwgrade.flush();
bwgrade.close();
}
}
Run:主函数类,对输入命令进行处理,调用相应的方法
package myapp;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Run {
public static void main(String[] args) throws IOException {
Scanner scan=new Scanner(System.in);
String command=null;
Create create=new Create();
Checkanswer checkanswer=new Checkanswer();
String nr="^\\-n\\s+\\d+\\s+\\-r\\s+\\d+$||^\\-r\\s+\\d+\\s+\\-n\\s+\\d+$";
String ea="^\\-e\\s+\\S+\\s+\\-a\\s+\\S+$";
int n = 0;
int r = 0;
String exercisesfile=null;
String answersfile=null;
System.out.println("小学四则运算题目生成程序");
System.out.println("-r 题目中数值(该参数可以设置为1或其他自然数)");
System.out.println("-n 题目个值(该参数可以设置为1或其他自然数)");
System.out.println("-e 需要批改的答案的文件路径");
System.out.println("-a 正确答案的文件路径");
System.out.println("请按照下面的格式输入命令");
System.out.println("例:-n 10 -r 10 或 -r 10 -n 10 (-r和-n命令需要一起使用)");
System.out.println("例:-e D:\\\\myanswer.txt -a D:\\\\Answers.txt (-e和-a命令需要一起使用)");
while(scan.hasNextLine()) {
if (scan.hasNextLine()) {
command = scan.nextLine();}
//检查命令是否正确
Pattern pa = Pattern.compile(nr);
Matcher ma = pa.matcher(command);
Pattern p = Pattern.compile(ea);
Matcher m = p.matcher(command);
if(!(ma.matches()||m.matches())) {
System.out.println("命令格式错误,请重新输入");
continue;
}
//对命令进行分割
String[] c=command.split("\\s+");
if(c[0].equals("-n")&&c[2].equals("-r")) {
n=Integer.parseInt(c[1]);
r=Integer.parseInt(c[3]);
create.cr(n,r);
System.out.println("练习题目Exercises.txt和答案文件Answers.txt已生成,放置在本程序的当前目录下");
}
else if(c[0].equals("-r")&&c[2].equals("-n")){
r=Integer.parseInt(c[1]);
n=Integer.parseInt(c[3]);
create.cr(n,r);
System.out.println("练习题目Exercises.txt和答案文件Answers.txt已生成,放置在本程序的当前目录下");
}
if(c[0].equals("-e")&&c[2].equals("-a")) {
exercisesfile=c[1];
answersfile=c[3];
try {
checkanswer.check(exercisesfile,answersfile);}
catch(FileNotFoundException e) {
System.out.println("找不到指定文件,请重新输入正确的文件路径");
continue;
}
System.out.println("批改文件Grade.txt已生成,放置在本程序的当前目录下");
}
System.out.println("请及时保存文件,再次使用程序时上一次生成的文件会被覆盖");
//归零
n=0;
r=0;
}
}
}
测试运行
生成10道题目:
统计答案中对错的的数目:
生成10000题目:
题目地址
答案地址
项目小结
此次项目,由我和夏浚杰同学一起结对编程,我们各负责一部分代码的编写。我们在设计功能时会提出各自的想法,经过讨论选择更合适的方法。在结对编程之后,我感受到了结对编程的优点在于,在我编写代码的过程中,夏浚杰同学会在旁边找出我的错误,让我能及时修改代码,这样提高了编程效率,节约时间。在结对编程中,夏浚杰同学提出了不同的想法,让我有所收获。