结对项目

软件工程 班级链接
作业要求 结对项目
github地址 Github
作业目标 实现一个自动生成小学四则运算题目的程序
姓名 学号
邓梓荣 3121005121
蔡嘉睿 3121005159

一、PSP表格

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

二、需求分析

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,它们之间不能通过有限次交换变成同一个题目。

三、性能分析

性能分析图:

四、设计实现过程

项目架构:

类和方法

1.ArithmeticUtil

描述:四则运算算法的生成和检测。

方法:buildArithmetic

描述:生成小学四则运算题目并写入Exercises.txt文件。

方法:checkExercise

描述:检查小学四则运算题目答案并将结果写入Grade.txt文件。

2.DataGenerator

描述:封装了生成随机数据的方法。

3.Fraction

描述:分数类,含有分数的约分,加减乘除等方法。

4.RPN

描述:使用逆波兰算法计算中缀表达式的值。

方法:calculateInfixExpression

描述:传入一个中缀表达式,返回计算结果。

流程图

五、代码说明

生成四则运算式子

/**
     * 生成小学四则运算题目并写入Exercises.txt文件
     *
     * @param exerciseNumber 生成个数
     * @param numberRange    题目中数值(自然数、真分数和真分数分母)的范围
     */
    public static void buildArithmetic(int exerciseNumber, int numberRange) {
        Random random = new Random();
        List<String> arithmeticList = new LinkedList<>();
        DataGenerator dataGenerator = new DataGenerator();
        for (int i = 1; i <= exerciseNumber; i++) {
            //限制每道题目中出现的运算符个数不超过3个
            int operatorNumber = random.nextInt(3)+1;
            StringBuilder arithmetic = new StringBuilder();
            switch (operatorNumber) {
                case 1:
                    // 9 + 5 arithmetic.append(dataGenerator.generateNumber(numberRange))
                            .append(dataGenerator.randomOperator())
                            .append(dataGenerator.generateNumber(numberRange))
                            .append(" =");
                    break;
                case 2:
                    //5 × 3 -2
                    arithmetic.append(dataGenerator.generateLeftParenthesis(1))
                            .append(dataGenerator.generateNumber(numberRange))
                            .append(dataGenerator.randomOperator())
                            .append(dataGenerator.generateLeftParenthesis(3))
                            .append(dataGenerator.generateNumber(numberRange))
                            .append(dataGenerator.generateRightParenthesis(4, 6))
                            .append(dataGenerator.randomOperator())
                            .append(dataGenerator.generateNumber(numberRange))
                            .append(dataGenerator.generateRightParenthesis(6, 6))
                            .append(" =");
                    break;
                case 3:
                    // 5 × 3 -2 ÷ 8
                    arithmetic.append(dataGenerator.generateLeftParenthesis(1))
                            .append(dataGenerator.generateNumber(numberRange))
                            .append(dataGenerator.randomOperator())
                            .append(dataGenerator.generateLeftParenthesis(3))
                            .append(dataGenerator.generateNumber(numberRange))
                            .append(dataGenerator.generateRightParenthesis(4, 8))
                            .append(dataGenerator.randomOperator())
                            .append(dataGenerator.generateLeftParenthesis(5))
                            .append(dataGenerator.generateNumber(numberRange))
                            .append(dataGenerator.generateRightParenthesis(6, 8))
                            .append(dataGenerator.randomOperator())
                            .append(dataGenerator.generateNumber(numberRange))
                            .append(dataGenerator.generateRightParenthesis(8, 8))
                            .append(" =");
                    break;
                default:
                    break;
            }
            try {
                RPN.calculateInfixExpression(arithmetic.toString());
                arithmeticList.add(i + ". " + arithmetic);
            } catch (Exception e) {
                i--;
            }
        }

        FileUtil.writeLines(arithmeticList,SubmissionParams.GENERATE_EXERCISE_FILE_NAME,"utf-8");
    }

这里使用了链式结构,不断往四则运算中添加符号,括号和数字,并对生成的结果进行验证,排除负数的情况,dataGenerator中封装了各种生成随机数据的方法,如下面生成真分数的方法:

    /**
     * 生成真分数 (真分数:1/2, 1/3, 2/3, 1/4, 1’1/2, …)
     *
     * @param numberRange
     * @return
     */
    public String randProperFraction(int numberRange) {
        int denominator = random.nextInt(numberRange - 1) + 1;
        int molecule = random.nextInt(numberRange * denominator - 1) + 1;
        if (molecule >= denominator) {
            int N = molecule / denominator;
            molecule %= denominator;
            return molecule == 0 ? String.valueOf(N) : N + "’" + molecule + "/" + denominator;
        }
        return molecule + "/" + denominator;
    }

最后把生成的四则运算写入到文件中。

计算四则运算式子并校对答案

 /**
     * 检查小学四则运算题目答案并将结果写入Grade.txt文件
     *
     * @param exerciseFileName 待检测题目文件名
     * @param answerFileName   待检测题目答案文件名
     */
    public static void checkExercise(String exerciseFileName, String answerFileName) {
        List<String> exercises = FileUtil.readLines(exerciseFileName, StandardCharsets.UTF_8);
        Map<String, String> answerMap = new HashMap<>();
        List<String> answers = FileUtil.readLines(answerFileName, StandardCharsets.UTF_8);
        String pattern = "(^[0-9]+). (.*)";
        Pattern r = Pattern.compile(pattern);
        answers.forEach(a -> {
            Matcher m = r.matcher(a);
            if (m.find()) {
                answerMap.put(m.group(1), m.group(2));
            }
        });
        List<String> trueExercise = new LinkedList<>();
        List<String> wrongExercise = new LinkedList<>();
        for (int i = 0; i < exercises.size(); i++) {
            Matcher m = r.matcher(exercises.get(i));
            if (m.find()) {
                String index = m.group(1);
                String arithmetic = m.group(2);
                String answer = RPN.calculateInfixExpression(arithmetic);
                if (answer.equals(answerMap.get(index))) {
                    trueExercise.add(index);
                } else {
                    wrongExercise.add(index);
                }
            }
        }
        StringBuilder fileContent = new StringBuilder().append("Correct: ").append(trueExercise.size()).append(" (");
        for (int i = 0; i < trueExercise.size(); i++) {
            fileContent.append(trueExercise.get(i)).append(i == trueExercise.size() - 1 ? "" : ",");
        }
        fileContent.append(")\n");
        fileContent.append("Wrong: ").append(wrongExercise.size()).append(" (");
        for (int i = 0; i < wrongExercise.size(); i++) {
            fileContent.append(wrongExercise.get(i)).append(i == wrongExercise.size() - 1 ? "" : ",");
        }
        fileContent.append(")");
        FileUtil.writeString(fileContent.toString(), SubmissionParams.CHECK_RESULT_FILE_NAME, "utf-8");
    }

读取文件获得四则运算式子和答案后,通过正则表达式获取真正的数据(序列号和式子/答案),然后通过逆波兰表达式计算出结果,并进行字符串对比,如果则计入trueExercise,否则计入wrongExercise,逆波兰计算后缀表达式过程如下:

 private static String calculate(List<String> ls) {
        // 创建一个栈,只需要一个栈即可
        Stack<String> stack = new Stack<>();
        // 遍历 ls
        for (String item : ls) {
            if (isNumber(item)) {
                stack.push(item);
            } else {
                // pop 出两个数并运算,在入栈
                String num2 = stack.pop();
                String num1 = stack.pop();
                String res;
                Fraction fraction1 = new Fraction(num1);
                Fraction fraction2 = new Fraction(num2);
                if ("+".equals(item)) {
                    res = fraction1.add(fraction2).toString();
                } else if ("-".equals(item)) {
                    res = fraction1.subtract(fraction2).toString();
                } else if ("×".equals(item)) {
                    res = fraction1.multiply(fraction2).toString();
                } else if ("÷".equals(item)) {
                    res = fraction1.divide(fraction2).toString();
                } else {
                    throw new RuntimeException("符号有问题");
                }
                stack.push(res);
            }
        }
        // 最后留在stack的数据是运算结果
        return stack.pop();
    }

六、测试运行

对程序进行测试生成至少10个测试用例:

判断答案对错:

七、项目小结

在这个四则运算编程项目中,我与我的合作伙伴先共同明确了项目的目标和要求,并进行了详细的讨论和规划。我们讨论了任务的分工和时间表,并确保每个人都理解自己的角色和责任。建立良好的沟通渠道是重中之重,我们一起讨论项目进展、及时解决遇到的问题,并共享想法和建议。我们互相支持,共同解决遇到的技术问题和挑战。我们充分利用了彼此的技能和专长,共同努力克服困难。通过此次项目我们学会了更好地协调和合作,提高了沟通和解决问题的能力,相互学习和成长。

posted @ 2023-09-27 20:18  LEGION000  阅读(32)  评论(0)    收藏  举报