结对编程--四则运算(Java)萧英杰 夏浚杰

结对编程--四则运算(Java)萧英杰 夏浚杰

Github项目地址

功能要求

题目:实现一个自动生成小学四则运算题目的命令行程序

  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. 程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,统计结果输出到文件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题目:

题目地址

答案地址

项目小结

此次项目,由我和夏浚杰同学一起结对编程,我们各负责一部分代码的编写。我们在设计功能时会提出各自的想法,经过讨论选择更合适的方法。在结对编程之后,我感受到了结对编程的优点在于,在我编写代码的过程中,夏浚杰同学会在旁边找出我的错误,让我能及时修改代码,这样提高了编程效率,节约时间。在结对编程中,夏浚杰同学提出了不同的想法,让我有所收获。

posted @ 2018-09-30 15:52  auxshaw  阅读(262)  评论(0编辑  收藏  举报