结对伙伴:陈振华

 

 项目要求

 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+33+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文件

表达式和答案输出到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.1Personal 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

 总结

  在这次的结对编程中,真正体会到了两个人不同思想的碰撞,虽然你一开始想出来的方法可行,但是别人想出来的可能更加的简便易实现。所以多跟他人交流沟通会有很大的收获。在这过程中,我和结对伙伴就生成表达式过程中该如何存储,如果结果为负数在生成过程中该如何实现不生成负数结果的表达式,分数的实现和计算,括号的优先运算,后缀表达式的实现进行了较深入的探讨。陈振华同学也针对我的编码风格和代码简约程度提出了批评意见,我也认识到这一点,需认真学习代码编写的简约清晰化,在后面的代码编写中更加注意这些问题。