结对编程:小学四则运算

1.成员介绍

姓名学号github代码库
马驭3121005047https://gitcode.net/mihuaAAA/3121005047/-/tree/master/Four-operation
郑乾睿3121005062https://gitcode.net/UKBigBen/3121005062/-/tree/master/Four-operation

2.项目需求

使用 -n 参数控制生成题目的个数,例如

Myapp.exe -n 10

将生成10个题目。

使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围,例如

Myapp.exe -r 10

将生成10以内(不包括10)的四则运算题目。该参数可以设置为1或其他自然数。该参数必须给定,否则程序报错并给出帮助信息。

1.生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1− e2的子表达式,那么e1≥ e2。
2.生成的题目中如果存在形如e1÷ e2的子表达式,那么其结果应是真分数。
3.每道题目中出现的运算符个数不超过3个。
4.程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。例如,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文件,格式如下:

1.四则运算题目1
2.四则运算题目2
……

其中真分数在输入输出时采用如下格式,真分数五分之三表示为3/5,真分数二又八分之三表示为2’3/8。

1.在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件,格式如下:

1.答案1
2.答案2

特别的,真分数的运算如下例所示:1/6 + 1/8 = 7/24。

1.程序应能支持一万道题目的生成。
2.程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,输入参数如下:

Myapp.exe -e .txt -a .txt

统计结果输出到文件Grade.txt,格式如下:

Correct: 5 (1, 3, 5, 7, 9)

Wrong: 5 (2, 4, 6, 8, 10)

其中“:”后面的数字5表示对/错的题目的数量,括号内的是对/错题目的编号。为简单起见,假设输入的题目都是按照顺序编号的符合规范的题目。

3.设计实现

项目结构:
在这里插入图片描述
生成文档:
在这里插入图片描述

函数调用:
FourOperation:
main(String[] args)
Calculate:
checkout(String formula, int length)
calculate(String m, String n, String operator)
greatFraction(int integralPart, int molecule, int denominator)
Check:
generate(int n,int r)
ifRepeat(String[] ansFormula)
Create:
createFormula(int r)
commonFactor(int a,int b)
TxtIOUtils
createProblemSet(int n,int r)
createExeFile(ArrayList txtList)
createAnsFile(ArrayList ansList)
createGradeFile(String submitPath, String answersPath)
output(PrintStream p,ArrayList quesNum)
obtainAnswer(String path)

4.代码说明

(1)入口函数:

package com.my.main;

import com.my.utils.TxtIOUtils;

import java.util.Scanner;

public class FourOperation {
    /**
     * 入口
     * @param args 指令字符串数组
     */
    public static void main(String[] args) {
        ord:
        while(true) {
            //若不输入指令直接按回车键,则默认生成10条运算数在10以内的算术题

            int n = 10;//运算题数量,默认为10
            int r = 10;//运算数范围,默认为[0,10)
            String submitPath = null;//题目文件路径
            String answersPath = null;//答案文件路径

            try {
                // 输入指令
                System.out.println("请输入需要执行的指令:(详情请看说明文档.txt)");
                Scanner command = new Scanner(System.in);
                String arr[] = command.nextLine().split("\\s"); //把输入的字符串以\\s为条件分割成一个String数组

                //指令数组长度>1时,获取正常指令
                if (arr.length > 1) {
                    for (int i = 0; i < arr.length; i = i + 2) {
                        switch (arr[i]) {
                            case "-n":
                                n = Integer.parseInt(arr[i + 1]);
                                if (n > 10000 || n < 1) {
                                    System.out.println("输入的n范围错误,这是生成题目的数量,请输入大于0的整数");
                                    return;
                                }
                                break;
                            case "-r":
                                r = Integer.parseInt(arr[i + 1]);
                                if (r < 1) {
                                    System.out.println("输入的r范围错误,这是题目中的数值范围,请输入大于0的整数");
                                    return;
                                }
                                break;
                            case "-e":
                                submitPath = arr[i + 1];
                                if (submitPath == null) {
                                    System.out.println("输入错误!!!找不到题目文件的路径");
                                    return;
                                }
                                break;
                            case "-a":
                                answersPath = arr[i + 1];
                                if (answersPath == null) {
                                    System.out.println("输入错误!!!找不到答案文件的路径");
                                    return;
                                }
                                break;
                            default:
                                System.out.println("输入的指令错误!!!");
                                break;
                        }
                    }
                }else if(arr[0].equals("exit")){//输入exit退出程序
                    System.out.println("退出程序");
                    System.exit(0);
                }
                else if(arr[0].length() != 0){//输入的不是任何指令时报错,重新输入指令
                    System.out.println("输入的指令错误!!!");
                    continue ord;
                }
            } catch (NumberFormatException e) {
                System.out.println("输入的指令错误!!!请重新输入");
                continue ord;
            }

            System.out.println("n: " + n + ", r: " + r);
            TxtIOUtils makefile = new TxtIOUtils();

            //文件不为空时,创建成绩文件;为空时,创建题目文件和答案文件
            if (submitPath != null && answersPath != null)
                makefile.createGradeFile(submitPath,answersPath);
            else
                makefile.createProblemSet(n,r);
        }
    }
}

(2)运算的主要代码设计:

package com.my.service;

import java.util.HashMap;
import java.util.Stack;

public class Calculate {

    /**
     *
     * @param formula 运算式
     * @param length 返回 RPN字符串数组的长度
     * @return RPN 含有后缀表达式、"="、运算结果和运算式的字符串数组
     */
    public String[] checkout(String formula, int length) {
        Stack<String> stackN = new Stack<>();//用栈来存放运算数
        Stack<String> stackO = new Stack<>();//用栈来存放运算符
        String[] RPN = new String[length];//逆波兰表达式(后缀表达式)
        // 哈希表存放运算符优先级
        HashMap<String, Integer> hashmap = new HashMap<>();
        hashmap.put("(", 0);
        hashmap.put("+", 1);
        hashmap.put("-", 1);
        hashmap.put("×", 2);
        hashmap.put("÷", 2);

        for (int i = 0, j = 0; i < formula.length(); ) {
            StringBuilder digit = new StringBuilder();//数字
            //将式子切割
            char c = formula.charAt(i);  //把运算式的第i个字符赋值给c
            //若c为数字,存入digit
            while (Character.isDigit(c) || c == '/' || c == '\'') { //数字、分数符号、带分数符号都要
                digit.append(c);
                i++;
                c = formula.charAt(i);
            }
            //digit里无数字,开始处理符号
            if (digit.length() == 0) {
                switch (c) {
                    //如果是“(”,就转化为字符串压入字符栈
                    case '(': {
                        stackO.push(String.valueOf(c));
                        break;
                    }
                    //遇到“)”了,则进行计算
                    case ')': {
                        String operator = stackO.pop();
                        //符号栈不为空,取操作数运算
                        while (!stackO.isEmpty() && !operator.equals("(")) {
                            String a = stackN.pop();
                            String b = stackN.pop();
                            //后缀表达式变形
                            RPN[j++] = a;
                            RPN[j++] = b;
                            RPN[j++] = operator;
                            String ansString = calculate(b, a, operator);
                            if (ansString == null)
                                return null;
                            //将结果压入栈
                            stackN.push(ansString);
                            //符号指向下一个计算符号
                            operator = stackO.pop();
                        }
                        break;
                    }
                    //遇到了“=”,计算最终结果
                    case '=': {
                        String operator;
                        while (!stackO.isEmpty()) {
                            operator = stackO.pop();
                            String a = stackN.pop();
                            String b = stackN.pop();
                            //后缀表达式变形
                            RPN[j++] = a;
                            RPN[j++] = b;
                            RPN[j++] = operator;
                            String ansString = calculate(b, a, operator);
                            if (ansString == null)
                                return null;
                            stackN.push(ansString);
                        }
                        break;
                    }
                    //其他
                    default: {
                        String operator;
                        while (!stackO.isEmpty()) {
                            operator = stackO.pop();
                            if (hashmap.get(operator) >= hashmap.get(String.valueOf(c))) { //比较优先级
                                String a = stackN.pop();
                                String b = stackN.pop();
                                //后缀表达式变形
                                RPN[j++] = a;
                                RPN[j++] = b;
                                RPN[j++] = operator;
                                String ansString = calculate(b, a, operator);
                                if (ansString == null)
                                    return null;
                                stackN.push(ansString);
                            } else {
                                stackO.push(operator);
                                break;
                            }

                        }
                        stackO.push(String.valueOf(c));  //将符号压入符号栈
                        break;
                    }
                }
            }
            //处理数字,直接压栈
            else {
                stackN.push(digit.toString());
                continue;
            }
            i++;
        }
        //栈顶数字为答案
        RPN[length - 3] = "=";
        RPN[length - 2] = stackN.peek();
        RPN[length - 1] = formula;
        return RPN;
    }

(3)检查运算式是否有重复:

package com.my.service;

import java.util.ArrayList;
import java.util.Arrays;

public class Check {
    private ArrayList<String> returnList = new ArrayList<>();
    private ArrayList<String> txtList = new ArrayList<>();
    private ArrayList<String> ansList = new ArrayList<>();
    private ArrayList<String[]> ansFoList = new ArrayList<>();


    /**
     * 生成暂存习题和答案,暂存的目的是提前检查运算式是否有重复
     * @param n 运算题数量
     * @param r 运算数范围
     * @return returnList 返回没有重复的运算式集(还含有未标序号的题和答案)
     */
    public ArrayList generate(int n,int r) {
        Create create = new Create();
        //生成n条不重复的运算式
        for(int i = 0 ; i < n;){
            //随机生成运算式
            String[] ansFormula = create.createFormula(r);
            //判断生成的运算式是否重复
            if (ansFormula != null) {
                if (!ifRepeat(ansFormula)) {
                    System.out.println((i+1)+":"+ansFormula[ansFormula.length-1]);
                    i++;
                }
            }
        }

        //把式子及运算结果添加到returnList
        for (int i = 0;i < 2*n;i++) {
            if(i < n) {
                returnList.add(txtList.get(i));
            } else {
                returnList.add(ansList.get(i-n));
            }
        }
        return returnList;
    }


    /**
     * 判断运算式是否重复
     * @param ansFormula 含有后缀表达式、运算结果和运算式的字符串数组
     * @return true(重复) / false(不重复)
     */
    private boolean ifRepeat(String[] ansFormula) {
        String formula = ansFormula[ansFormula.length-1];
        String[] rPNotation = new String[ansFormula.length-1];
        System.arraycopy(ansFormula, 0, rPNotation, 0, ansFormula.length-1);
        boolean ifRepeat = false;

        for (String[] ansFo: ansFoList) {
            if (Arrays.equals(ansFo,rPNotation)) {
                ifRepeat = true;
            } else if (ansFo.length == rPNotation.length && ansFo[ansFo.length-1].equals(rPNotation[rPNotation.length-1])){//若运算结果及长度一致,则式子可能重复,进一步比较
                int j;
                for (j=0;j<rPNotation.length-2;) {
                    boolean opRight = ansFo[j+2].equals("+")||ansFo[j+2].equals("×");
                    boolean exRight = ansFo[j].equals(rPNotation[j + 1]) && ansFo[j + 1].equals(rPNotation[j]) && ansFo[j + 2].equals(rPNotation[j + 2]);
                    boolean copRight = ansFo[j].equals(rPNotation[j]) && ansFo[j + 1].equals(rPNotation[j + 1]) && ansFo[j + 2].equals(rPNotation[j + 2]);
                    //运算符前后两个操作数交换比较
                    if (exRight&&opRight) {
                        j = j + 3;
                    } else if (copRight) {
                        j = j + 3;
                    } else {
                        break;
                    }
                }
                if (j == rPNotation.length-2) {
                    ifRepeat = true;
                    break;
                }
            }
        }

        if (!ifRepeat) {    //运算式不重复
            this.txtList.add(formula);
            this.ansList.add(rPNotation[rPNotation.length-1]);
            this.ansFoList.add(rPNotation);
        }
        return ifRepeat;
    }
}

5.效能分析

(1)Overview
在这里插入图片描述
(2)调用情况
在这里插入图片描述
(3)代码覆盖率
在这里插入图片描述

6.测试运行

1.测试生成10条运算数在10以内的算术题:
在这里插入图片描述
存储习题文档Exercises.txt:
在这里插入图片描述
习题答案文档Answer.txt:
在这里插入图片描述
2.判定对错:
学生答案:
在这里插入图片描述
对比指令:
在这里插入图片描述
结果数量统计:
在这里插入图片描述
3.生成一万道题目
在这里插入图片描述
在这里插入图片描述

7.PSP表格

PSP2.1Personal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划120120
Estimate估计这个任务需要多少时间300360
Development开发120150
Analysis需求分析 (包括学习新技术)3060
Design Spec生成设计文档3030
Design Review设计复审1010
Coding Standard代码规范 (为目前的开发制定合适的规范)1020
Design具体设计3050
Coding具体编码150180
Code Review代码复审1020
Test测试(自我测试,修改代码,提交修改)3040
Reporting报告120180
Test Repor测试报告3030
Size Measurement计算工作量1015
Postmortem & Process Improvement Plan事后总结, 并提出过程改进计划1020
合计10101320

8.项目小结

在这次经历的结对项目中,我们双方明确分工,分别负责代码和报告,在不断的交流中,能够及时地知道彼此工作的进程,并一起为了实现最终结果而努力工作。过程中也有一些不足,会通过双方的交流去解决,这是个人项目所不能体验的,所以这次的结对编程对我们来说是一个宝贵的经验。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值