结对项目:自动生成小学四则运算题目

发布时间 2023-09-28 11:24:31作者: ab***c
软件工程 https://edu.cnblogs.com/campus/gdgy/CSGrade21-12?filter=all
作业要求 https://edu.cnblogs.com/campus/gdgy/CSGrade21-12/homework/13016
作业目标 学习算法,完成简单的四则运算

团队成员:

姓名:陈鑫杰
学号:3121004688
姓名:黄嘉俊
学号:3121004696
Git链接:https://gitee.com/heart-knot/operation

题目

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

需求

1.使用 -n 参数控制生成题目的个数,例如
Myapp.exe -n 10
将生成10个题目。

2.使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围,例如
Myapp.exe -r 10
将生成10以内(不包括10)的四则运算题目。该参数可以设置为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文件,格式如下:

  • 1.四则运算题目1
  • 2.四则运算题目2
    ……
    其中真分数在输入输出时采用如下格式,真分数五分之三表示为3/5,真分数二又八分之三表示为2’3/8。

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

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

8.程序应能支持一万道题目的生成。

9.程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,输入参数如下:
Myapp.exe -e .txt -a .txt
统计结果输出到文件Grade.txt,格式如下:
Correct: 5 (1, 3, 5, 7, 9)
Wrong: 5 (2, 4, 6, 8, 10)
其中“:”后面的数字5表示对/错的题目的数量,括号内的是对/错题目的编号。为简单起见,假设输入的题目都是按照顺序编号的符合规范的题目。

PSP

PSP2.1 Personal Software Process Stages 预估耗费时间 (分钟) 实际耗时 (分钟)
Planning 计划 40 60
Estimate 估计这个任务需要多少时间 120 150
Development 开发 240 300
Analysis 需求分析 (包括学习新技术) 80 80
Design Spec 生成设计文档 40 50
Design Review 设计复审 30 30
Coding Standard 代码规范 (为目前的开发制定合适的规范) 20 15
Design 具体设计 60 60
Coding 具体编码 100 120
Code Review 代码复审 30 20
Test 测试(自我测试,修改代码,提交修改) 40 60
Reporting 报告 60 80
Test Report 测试报告 40 40
Size Measurement 计算工作量 20 20
Postmortem & Process Improvement Plan 事后总结, 并提出过程改进计划 20 25
Total 合计 940 1100

设计实现

流程图

结构设计:

Operation类:输入参数启动程序


public class Operation {
    public static void main(String[] args) throws IOException {
        //输入
        Scanner scanner = new Scanner(System.in);
        //题目数量
        System.out.println("请输入题目数量:");
        int num = scanner.nextInt();
        //数值范围
        System.out.println("请输入题目数值范围:");
        int range = scanner.nextInt();
        Map<String, String[]> map = TopicUtils.generateTopic(num, range);
        FileUtils.writeTA("Exercises", map.get("topics"));
        FileUtils.writeTA("Answers", map.get("answers"));
        String[] topics = map.get("topics");
        String[] answers = map.get("answers");
        String[] results = new String[topics.length];
        for (int i = 0; i < topics.length; i++) {
            int j = i + 1;
            System.out.println("题目" + j + ": " + topics[i]);
            System.out.println("请输入你的答案:");
            results[i] = scanner.next();
        }
        int[] rightCount = new int[results.length];
        int right = 0;
        int[] wrongCount = new int[results.length];
        int wrong = 0;
        for (int i = 0; i < results.length; i++) {
            if (results[i].equals(answers[i])) {
                rightCount[i] = i + 1;
                right++;
            } else {
                wrongCount[i] = i + 1;
                wrong++;
            }
        }
        FileUtils.writeG(rightCount, right, wrongCount, wrong);
    }
}

TopicUtils类:生成题目并计算答案:

    static Random random = new Random();

    /**
     * 生成题目并计算答案
     *
     * @param num   题目数量
     * @param range 题目数值范围
     * @return 题目
     */
    public static Map<String, String[]> generateTopic(int num, int range) {
        Map<String, String[]> map = new HashMap<>();
        String[] expressions = new String[num];
        for (int i = 0; i < num; i++) {
            //运算符个数
            int operatorNum = random.nextInt(2) + 1;
            System.out.println("operatorNum:" + operatorNum);
            //记录运算符
            int[] operators = new int[operatorNum];
            for (int j = 0; j < operatorNum; j++) {
                //随机生成运算符
                int operator = random.nextInt(3);
                operators[j] = operator;
            }
            String[] operands = new String[operatorNum + 1];
            //运算数生成
            for (int k = 0; k <= operatorNum; k++) {
                String operand = generateNum(range);
                operands[k] = operand;
            }
            //表达式生成
            StringBuilder expression = new StringBuilder();
            for (int j = 0; j < operatorNum; j++) {
                expression.append(operands[j]);
                if (operators[j] == 0) {
                    expression.append("+");
                } else if (operators[j] == 1) {
                    //减号可能会出现负数,就修改为整数
                    while (operands[j].compareTo(operands[j + 1]) < 0) {
                        operands[j + 1] = generateNum(range);
                    }
                    expression.append("-");
                } else if (operators[j] == 2) {
                    expression.append("*");
                } else {
                    expression.append("÷");
                }
            }
            expression.append(operands[operatorNum]);
            expressions[i] = expression.toString();
        }
        map.put("topics", expressions);
        String[] compute = compute(expressions);
        map.put("answers", compute);
        return map;
    }

    private static String generateNum(int range) {
        //判断是否生成分数
        boolean b = random.nextBoolean();
        //生成分数
        if (b) {
            //分子
            int numerator = random.nextInt(range);
            //分母
            int denominator = random.nextInt(range - 1) + 1;
            //拼接
            return reductionOfFraction(numerator, denominator);
        } else {//生成自然数
            int operand = random.nextInt(range);
            return String.valueOf(operand);
        }
    }

    /**
     * 计算答案
     *
     * @param expressions 题目
     * @return 答案
     */
    private static String[] compute(String[] expressions) {
        String[] answers = new String[expressions.length];
        int k = 0;
        for (String expression : expressions) {
            int j = 0;
            int[] operators = new int[4];
            for (int i = 0; i < expression.length(); i++) {
                char c = expression.charAt(i);
                if (c == '+' || c == '-' || c == '*' || c == '÷') {
                    //记录符号位置
                    operators[j++] = i;
                }
            }
            String result = "0";
            boolean flag = true;
            for (int i = 0; i < operators.length; i++) {
                System.out.println("k:" + k + "operators:" + operators[i]);
                if (operators[i] != 0 || flag) {
                    //第一个运算符
                    if (i == 0) {
                        result = expression.substring(0, operators[i]);
                    } else {
                        char c = expression.charAt(operators[i - 1]);
                        String substring;
                        if (operators[i] == 0) {
                            substring = expression.substring(operators[i - 1] + 1);
                        } else {
                            substring = expression.substring(operators[i - 1] + 1, operators[i]);
                        }
                        if (c == '+') {
                            result = addCompute(result, substring);
                        } else if (c == '-') {
                            result = cutCompute(result, substring);
                        } else if (c == '*') {
                            result = multiCompute(result, substring);
                        } else if (c == '÷') {
                            result = divisCompute(result, substring);
                        }
                    }
                    if (operators[i] == 0) {
                        flag = false;
                    }
                }
            }
            answers[k++] = result;
        }
        return answers;
    }

    private static String divisCompute(String result, String substring) {
        System.out.println("sub:" + substring);
        System.out.println("result:" + result);
        String mark1;
        //第二个运算数为带分数
        if (substring.contains(" ")) {
            //获取带分数前的整数
            int index = substring.indexOf(" ");
            System.out.println("index:" + index);
            String substring1 = substring.substring(0, index);
            //获取带分数的分数部分
            String substring2 = substring.substring(index + 1);
            //获取分子分母
            String[] split = substring2.split("/");
            int i3 = Integer.parseInt(split[1]) * Integer.parseInt(substring1) + Integer.parseInt(split[0]);
            mark1 = i3 + "/" + split[1];
        } else {//其他情况
            mark1 = substring;
        }
        String mark2;
        if (result.contains(" ")) {
            //获取带分数前的整数
            int index = result.indexOf(" ");
            System.out.println("index:" + index);
            String substring1 = result.substring(0, index);
            //获取带分数的分数部分
            String substring2 = result.substring(index + 1);
            //获取分子分母
            String[] split = substring2.split("/");
            int i3 = Integer.parseInt(split[1]) * Integer.parseInt(substring1) + Integer.parseInt(split[0]);
            mark2 = i3 + "/" + split[1];
        } else {
            mark2 = result;
        }
        int i1 = mark1.indexOf("/");
        int i2 = mark2.indexOf("/");
        System.out.println("mark1:" + mark1);
        System.out.println("mark2:" + mark2);
        //都为整数
        if (i1 < 0 && i2 < 0) {
            result = reductionOfFraction(Integer.parseInt(mark2), Integer.parseInt(mark1));
        } else if (i1 >= 0 && i2 < 0) {//一个分数
            String[] split = mark1.split("/");
            result = reductionOfFraction(Integer.parseInt(mark2) * Integer.parseInt(split[1]), Integer.parseInt(split[0]));
        } else if (i1 < 0) {//一个分数
            String[] split = mark2.split("/");
            result = reductionOfFraction(Integer.parseInt(split[0]), Integer.parseInt(split[1]) * Integer.parseInt(mark1));
        } else {//都为分数
            String[] split1 = mark1.split("/");
            String[] split2 = mark2.split("/");
            result = reductionOfFraction(Integer.parseInt(split1[1]) * Integer.parseInt(split2[0]), Integer.parseInt(split1[0]) * Integer.parseInt(split2[1]));
        }
        return result;
    }

    /**
     * 乘法
     *
     * @param result    前一步结果
     * @param substring 运算数
     * @return 结果
     */
    private static String multiCompute(String result, String substring) {
        System.out.println("sub:" + substring);
        System.out.println("result:" + result);
        String mark1;
        //第二个运算数为带分数
        if (substring.contains(" ")) {
            //获取带分数前的整数
            int index = substring.indexOf(" ");
            System.out.println("index:" + index);
            String substring1 = substring.substring(0, index);
            //获取带分数的分数部分
            String substring2 = substring.substring(index + 1);
            //获取分子分母
            String[] split = substring2.split("/");
            int i3 = Integer.parseInt(split[1]) * Integer.parseInt(substring1) + Integer.parseInt(split[0]);
            mark1 = i3 + "/" + split[1];
        } else {//不为带分数
            mark1 = substring;
        }
        String mark2;
        if (result.contains(" ")) {
            //获取带分数前的整数
            int index = result.indexOf(" ");
            System.out.println("index:" + index);
            String substring1 = result.substring(0, index);
            //获取带分数的分数部分
            String substring2 = result.substring(index + 1);
            //获取分子分母
            String[] split = substring2.split("/");
            int i3 = Integer.parseInt(split[1]) * Integer.parseInt(substring1) + Integer.parseInt(split[0]);
            mark2 = i3 + "/" + split[1];
        } else {
            mark2 = result;
        }
        System.out.println("mark1:" + mark1);
        System.out.println("mark2:" + mark2);
        int i1 = mark1.indexOf("/");
        int i2 = mark2.indexOf("/");
        //都为整数
        if (i1 < 0 && i2 < 0) {
            result = Integer.parseInt(mark1) * Integer.parseInt(mark2) + "";
        } else if (i1 >= 0 && i2 < 0) {//一个分数
            String[] split = mark1.split("/");
            result = reductionOfFraction(Integer.parseInt(mark2) * Integer.parseInt(split[0]), Integer.parseInt(split[1]));
        } else if (i1 < 0) {//一个分数
            String[] split = mark2.split("/");
            result = reductionOfFraction(Integer.parseInt(split[0]) * Integer.parseInt(mark1), Integer.parseInt(split[1]));
        } else {//都为分数
            String[] split1 = mark1.split("/");
            String[] split2 = mark2.split("/");
            result = reductionOfFraction(Integer.parseInt(split1[0]) * Integer.parseInt(split2[0]), Integer.parseInt(split1[1]) * Integer.parseInt(split2[1]));
        }
        return result;
    }

    /**
     * 减法
     *
     * @param result    前一步结果
     * @param substring 运算数
     * @return 结果
     */
    private static String cutCompute(String result, String substring) {
        System.out.println("sub:" + substring);
        System.out.println("result:" + result);
        String mark1;
        //第二个运算数为带分数
        if (substring.contains(" ")) {
            //获取带分数前的整数
            int index = substring.indexOf(" ");
            System.out.println("index:" + index);
            String substring1 = substring.substring(0, index);
            //获取带分数的分数部分
            String substring2 = substring.substring(index + 1);
            //获取分子分母
            String[] split = substring2.split("/");
            int i3 = Integer.parseInt(split[1]) * Integer.parseInt(substring1) + Integer.parseInt(split[0]);
            mark1 = i3 + "/" + split[1];
        } else {//不为带分数
            mark1 = substring;
        }
        String mark2;
        if (result.contains(" ")) {
            //获取带分数前的整数
            int index = result.indexOf(" ");
            System.out.println("index:" + index);
            String substring1 = result.substring(0, index);
            //获取带分数的分数部分
            String substring2 = result.substring(index + 1);
            //获取分子分母
            String[] split = substring2.split("/");
            int i3 = Integer.parseInt(split[1]) * Integer.parseInt(substring1) + Integer.parseInt(split[0]);
            mark2 = i3 + "/" + split[1];
        } else {
            mark2 = result;
        }
        int i1 = mark1.indexOf("/");
        int i2 = mark2.indexOf("/");
        System.out.println("mark1:" + mark1);
        System.out.println("mark2:" + mark2);
        //都为整数
        if (i1 < 0 && i2 < 0) {
            int i = Integer.parseInt(mark2) - Integer.parseInt(mark1);
            result = i + "";
        } else if (i1 >= 0 && i2 < 0) {//一个分数
            String[] split = mark1.split("/");
            result = reductionOfFraction(Integer.parseInt(mark2) * Integer.parseInt(split[1]) - Integer.parseInt(split[0]), Integer.parseInt(split[1]));
        } else if (i1 < 0) {//一个分数
            String[] split = mark2.split("/");
            result = reductionOfFraction(Integer.parseInt(split[0]) - Integer.parseInt(split[1]) * Integer.parseInt(mark1), Integer.parseInt(split[1]));
        } else {//都为分数
            String[] split1 = mark1.split("/");
            String[] split2 = mark2.split("/");
            result = reductionOfFraction(Integer.parseInt(split2[0]) * Integer.parseInt(split1[1]) - Integer.parseInt(split1[0]) * Integer.parseInt(split2[1]), Integer.parseInt(split1[1]) * Integer.parseInt(split2[1]));
        }
        return result;
    }

    private static String addCompute(String result, String substring) {
        System.out.println("sub:" + substring);
        System.out.println("result:" + result);
        String mark1;
        //第二个运算数为带分数
        if (substring.contains(" ")) {
            //获取带分数前的整数
            int index = substring.indexOf(" ");
            System.out.println("index:" + index);
            String substring1 = substring.substring(0, index);
            //获取带分数的分数部分
            String substring2 = substring.substring(index + 1);
            //获取分子分母
            String[] split = substring2.split("/");
            int i3 = Integer.parseInt(split[1]) * Integer.parseInt(substring1) + Integer.parseInt(split[0]);
            mark1 = i3 + "/" + split[1];
        } else {//不为带分数
            mark1 = substring;
        }
        String mark2;
        if (result.contains(" ")) {
            //获取带分数前的整数
            int index = result.indexOf(" ");
            System.out.println("index:" + index);
            String substring1 = result.substring(0, index);
            //获取带分数的分数部分
            String substring2 = result.substring(index + 1);
            //获取分子分母
            String[] split = substring2.split("/");
            int i3 = Integer.parseInt(split[1]) * Integer.parseInt(substring1) + Integer.parseInt(split[0]);
            mark2 = i3 + "/" + split[1];
        } else {
            mark2 = result;
        }
        int i1 = mark1.indexOf("/");
        int i2 = mark2.indexOf("/");
        System.out.println("mark1:" + mark1);
        System.out.println("mark2:" + mark2);
        //都为整数
        if (i1 < 0 && i2 < 0) {
            int i = Integer.parseInt(mark1) + Integer.parseInt(mark2);
            result = i + "";
        } else if (i1 >= 0 && i2 < 0) {//一个分数
            String[] split = mark1.split("/");
            result = reductionOfFraction(Integer.parseInt(mark2) * Integer.parseInt(split[1]) + Integer.parseInt(split[0]), Integer.parseInt(split[1]));
        } else if (i1 < 0) {//一个分数
            String[] split = mark2.split("/");
            result = reductionOfFraction(Integer.parseInt(split[1]) * Integer.parseInt(mark1) + Integer.parseInt(split[0]), Integer.parseInt(split[1]));
        } else {//都为分数
            String[] split1 = mark1.split("/");
            String[] split2 = mark2.split("/");
            result = reductionOfFraction(Integer.parseInt(split1[0]) * Integer.parseInt(split2[1]) + Integer.parseInt(split2[0]) * Integer.parseInt(split1[1]), Integer.parseInt(split1[1]) * Integer.parseInt(split2[1]));
        }
        return result;
    }

    /**
     * 约分
     *
     * @param numerator   分子
     * @param denominator 分母
     * @return
     */
    private static String reductionOfFraction(int numerator, int denominator) {
        System.out.println("numerator:" + numerator);
        System.out.println("denominator" + denominator);
        int y = 1;
        for (int i = numerator; i >= 1; i--) {
            if (numerator % i == 0 && denominator % i == 0) {
                y = i;
                break;
            }
        }
        // 分子
        int z = numerator / y;
        // 分母
        int m = denominator / y;
        System.out.println("z:" + z);
        System.out.println("m:" + m);
        if (z == 0) {
            return "0";
        }
        if (m == 1) {
            return z + "";
        } else {
            return convertToValid(z, m);
        }
    }

    /**
     * 假分数转为带分数
     *
     * @param numerator   分子
     * @param denominator 分母
     * @return
     */
    private static String convertToValid(int numerator, int denominator) {
        if (numerator >= denominator) {
            int i = numerator / denominator;
            int j = numerator % denominator;
            if (j == 0) {
                return i + "";
            } else {
                return i + " " + j + "/" + denominator;
            }
        } else {
            return numerator + "/" + denominator;
        }
    }
}

FileUtils类:文件操作

public class FileUtils {
    public static void writeTA(String name, String[] strings) throws IOException {
        FileWriter fw;
        //题目写入
        File f = new File(name + ".txt");
        fw = new FileWriter(f, true);
        int i = 0;
        PrintWriter pw = new PrintWriter(fw);
        for (String string : strings) {
            pw.println(i + 1 + ". " + string);
            pw.flush();
            i += 1;
        }
        fw.flush();
        pw.close();
        fw.close();
    }

    public static void writeG(int[] rightCount, int right, int[] wrongCount, int wrong) throws IOException {
        FileWriter fw;
        File f = new File("Grade.txt");
        fw = new FileWriter(f, true);
        PrintWriter pw = new PrintWriter(fw);
        pw.println(" ");
        pw.print("Correct:" + right + "(");
        for (int i = 0; i < rightCount.length; i++) {
            if (rightCount[i] != 0 && i != rightCount.length - 1) {
                pw.print(rightCount[i] + ",");
            } else if (rightCount[i] != 0 && i == rightCount.length - 1) {
                pw.print(rightCount[i]);
            }
        }
        pw.println(")");
        pw.print("Wrong:" + wrong + "(");
        for (int j = 0; j < wrongCount.length; j++) {
            if (wrongCount[j] != 0 && j != wrongCount.length - 1) {
                pw.print(wrongCount[j] + ",");
            } else if (wrongCount[j] != 0 && j == wrongCount.length - 1) {
                pw.print(wrongCount[j]);
            }
        }
        pw.print(")");
        pw.flush();
        fw.flush();
        pw.close();
        fw.close();
    }
}

程序测试:

1.输入参数:

2.输入题目答案:

3.生成的文档:

性能分析:

项目小结:

在这次合作中,我们获得了不错的成长。学会了项目分工,每个人各司其职,才能搭建好一个完整的项目。期间遇到的困难也积极解决,在沟通和协调中提升了自己的业务能力和沟通能力。这次项目是非常好的体验,为我们今后的团队合作打下了基础,同时感谢一起合作的小伙伴为这次的结对项目的辛勤付出。