结对项目

一、作业信息

这个作业属于哪个课程 软件工程
这个作业要求在哪里 结对项目
这个作业的目标 实现一个自动生成小学四则运算题目的命令行程序

姓名 学号 github
陈国金 3122004301 https://github.com/guzhouyiye/MathFormulaGenerator
廖俊龙 3118005817 https://github.com/joey-long/MathFormulaGenerator

二、PSP表格

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

三、模块接口的设计与实现过程

项目类与方法设计

1.MainMathFormulaGenerator类

用于接收命令行参数,调用其他工具类实现生成四则运算题目功能

2.CommandLineParser类

用于读取命令行参数,并对参数输入出现的问题进行输出。get方法用来得到所要的参数,has方法用来判断某个参数是否存在

3.EvaluateExpression类

  用于计算运算式的类。其中expressionResult方法是主方法,其他类通过调用这个方法来获得计算结果。countExpression方法由expressionResult调用,根据提供的参数对某个范围的运算式进行计算。count方法由countExpression方法调用,用于两个数中间的加减乘除运算。commonDenominator方法用于给分数通分,由count调用,方便分数之间的运算。

4.existJudge类

负责判断运算式是否已经存在的类,通过对每两个式子进行compareNumberCount,compareResult,compareAllOperatorFix,compareAllNumberFix四个方法的调用比较来确定是否存在。

5.Expression类

  负责生成运算式的类。Expression为该类的构造函数。generateNumber方法 随机生成自然数或真分数;simplify方法用于约分,gcd方法由simplify调用,来求最大公因数;generateOperator方法随机生成运算符;generateBracket随机生成括号。

6.ExpressionSet类

  负责统筹所有运算式生成并写入文件的类。ExpressionSet构造函数由main方法调用,会通过调用其他工具类,根据参数生成对应数量的合法的运算式。并通过expressionsWrite方法和answersWrite方法将要写进文件的内容添加到字符串中,最后将字符串写入文件

7.JudgeExpressions类

  负责判题的类。JudgeExpressions为构造函数,由main方法调用,通过调用其他工具类,判断并记录题目对错信息,最后写入文件。writeResult方法将将判题结果写入文件;rightJudge方法比较答案是否相同;separateExpression方法将运算式拆分成运算式、运算符、括号,然后调用EvaluateExpression类进行计算;getNumber将字符串里的运算数转化为Number类。

8.Number

用于创建对象,Number对象以分数形式保存运算数和答案,方便统一运算数的计算

流程图

四、效能分析

使用IDEA内置的IntelliJ Profile进行性能分析

1.热点图

2.方法列表

程序中消耗最大的函数

性能改进

消耗最大的方法是关于判断运算式是否出现过。改进时切换判断的顺序,使判断更高效。消耗时间从130ms减少到70ms

五、代码说明

ExpressionSet类,根据参数批量生成运算式,符合要求则添加,不符要求会排除重新生成。最后把运算式即答案写入文件。

import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

// 负责统筹所有运算式生成并写入文件的类
public class ExpressionSet {

    // 所有式子的列表
    List<Expression> allExpression = new ArrayList<>();

    // 创建文件
    FileWriter expressionsTxt = new FileWriter("Exercises.txt");
    FileWriter answersTxt = new FileWriter("Answers.txt");

    StringBuilder expressionsString = new StringBuilder();
    StringBuilder answersString = new StringBuilder();


    public ExpressionSet(int questionCount, int range) throws IOException {

        // 生成questionCount条式子
        for (int i = 0; i < questionCount; i++) {
            // 生成一条式子
            Expression expression = new Expression(range);

            // 如果式子结果不是负数且没有出现过
            ExistJudge existJudge = new ExistJudge();
            if (expression.result.denominator > 0 && !existJudge.existJudge(allExpression, expression)) {
                allExpression.add(expression);
                // 添加运算式到运算式字符串中
                expressionsWrite(expressionsString, allExpression.get(i), i);
                // 添加答案到答案字符串中
                answersWrite(answersString, allExpression.get(i), i);
            }
            // 如果不合法
            else {
                i--;
            }
        }
        expressionsTxt.write(expressionsString.toString());
        answersTxt.write(answersString.toString());
        expressionsTxt.close();
        answersTxt.close();
    }

    // 运算式写入运算式字符串
    private void expressionsWrite(StringBuilder expressionsString, Expression expression, int i) {
        expressionsString.append(i + 1).append(". ");
        // 同个位置判断顺序:'(' 运算数 运算符 ')'
        // 左右括号判断(0左1右)
        int judgeBracket = 0;
        for (int j = 0; j < expression.numberCount; j++) {
            // 添加左括号
            if (expression.allBracket.size() == 2 && expression.allBracket.get(0) == j && judgeBracket == 0) {
                expressionsString.append("(");
                judgeBracket++;
            }
            // 添加运算数
            // 自然数
            if (expression.allNumberFix.get(j).denominator == 1 || expression.allNumberFix.get(j).numerator == 0) {
                expressionsString.append(expression.allNumberFix.get(j).numerator);
            }
            // 分数
            else {
                // 判断真假分数
                int judge = expression.allNumberFix.get(j).numerator / expression.allNumberFix.get(j).denominator;
                // judge<1,真分数
                if (judge < 1) {
                    expressionsString.append(expression.allNumberFix.get(j).numerator).append("/").append(expression.allNumberFix.get(j).denominator);
                }
                // judge>=1,假分数
                else {
                    // 提取假分数的整数部分judge后,需要修改分数部分的分子
                    int fractionNumerator = expression.allNumberFix.get(j).numerator % expression.allNumberFix.get(j).denominator;
                    Number temp = expression.simplify(new Number(fractionNumerator, expression.allNumberFix.get(j).denominator));
                    expressionsString.append(judge).append("’").append(temp.numerator).append("/").append(temp.denominator);
                }
            }
            // 添加右括号
            if (expression.allBracket.size() == 2 && expression.allBracket.get(1) == j + 1 && judgeBracket == 1) {
                judgeBracket++;
                expressionsString.append(")");
            }
            // 添加运算符
            if (j < expression.numberCount - 1) {
                expressionsString.append(" ").append(expression.allOperatorFix.get(j)).append(" ");
            }
        }
        expressionsString.append(" =" + "\n");
    }

    // 答案写入答案字符串
    private void answersWrite(StringBuilder answersString, Expression expression, int i) {
        answersString.append(i + 1).append(". ");
        // 自然数
        if (expression.result.denominator == 1) {
            answersString.append(expression.result.numerator).append("\n");

        }
        // 分数
        else {
            // 判断真假分数
            int judge = expression.result.numerator / expression.result.denominator;
            // judge<1,真分数
            if (judge < 1) {
                answersString.append(expression.result.numerator).append("/").append(expression.result.denominator).append("\n");
            }
            // judge>=1,假分数
            else {
                // 提取假分数的整数部分judge后,需要修改分数部分的分子
                int fractionNumerator = expression.result.numerator % expression.result.denominator;
                answersString.append(judge).append("’").append(fractionNumerator).append("/").append(expression.result.denominator).append("\n");
            }
        }
    }
}

Expression类,用于随机生成运算式中的运算数、运算符和括号位置,十分关键。

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

// 负责生成运算式的类
public class Expression {
    private final Random random = new Random();
    
    int numberCount = random.nextInt(3) + 2;    // 运算式的数量(2,3,4)
    List<Number> allNumber = new ArrayList<>();       // 运算式列表
    List<Character> allOperator = new ArrayList<>();  //  运算符列表
    List<Integer> allBracket = new ArrayList<>();     // 括号位置列表,位于相同索引运算数的左边

    // 用来复制上面两个列表,作为固定列表,在判断运算式是否已经存在时使用
    List<Number> allNumberFix = new ArrayList<>();
    List<Character> allOperatorFix = new ArrayList<>();

    // 用来存储运算结果
    Number result = new Number(); 

    public Expression(int range) {
        // 生成数字、符号、括号列表
        generateNumber(range);
        generateOperator();
        generateBracket();
        EvaluateExpression evaluateExpression = new EvaluateExpression();
        result = evaluateExpression.expressionResult(allNumber, allOperator, allBracket, 0, allOperator.size());
    }
    public Expression() {
    }

    // 随机生成自然数或真分数
    private void generateNumber(int range) {
        for (int i = 0; i < numberCount; i++) {
            Number number = new Number();
            boolean isFraction = random.nextBoolean();
            // 生成分数(分母不为0)
            if (isFraction) {
                number.numerator = random.nextInt(range * (range - 1) -1) + 1;

                //
                int productNumberCount = 0;
                do{
                    number.denominator = random.nextInt(range - 1) + 1;
                    if(productNumberCount++ == 10000000) System.out.println("参数r太小啦");
                }while(number.numerator % number.denominator == 0 || number.numerator / number.denominator >= range);
                // 调用simplify方法化简分数
                number = simplify(number);
            }
            // 生成自然数
            else {
                number.numerator = random.nextInt(range - 1) + 1;
                number.denominator = 1;
            }
            allNumber.add(number);
        }
        // 深拷贝 allNumber 到 allNumberFix
        for (Number num : allNumber) {
            // 这里为每个 num 创建一个新的 Number 对象,确保 allNumberFix 拷贝的是新的对象
            allNumberFix.add(new Number(num.numerator, num.denominator));
        }

    }

    // 约分
    public Number simplify(Number number) {
        int numerator = number.numerator;
        int denominator = number.denominator;
        int gcd = gcd(numerator, denominator);
        number.numerator = numerator / gcd;
        number.denominator = denominator / gcd;
        return number;
    }
    // 求最大公因数
    private int gcd(int a, int b) {
        return b == 0 ? a : gcd(b, a % b);
    }

    // 随机生成运算符
    private void generateOperator(){
        Character[] operator = new Character[]{'+', '-', '×', '÷'}; //运算符号
        // 运算符数量是运算式数量减一
        for (int i = 0; i < numberCount - 1; i++) {
            allOperator.add(operator[random.nextInt(operator.length)]);
        }
        allOperatorFix = new ArrayList<>(allOperator);
    }

    // 随机生成括号
    private void generateBracket(){
        // 括号个数0或1
        if(numberCount > 2 && random.nextBoolean()) {
            int index = random.nextInt(numberCount - 1);
            allBracket.add(index);
            allBracket.add(index + 2);
        }
    }
}

六、测试说明

参数缺失


参数范围不正确


正确的输入输出

-n,-r参数



-e,-a参数

用标准答案,所以全部正确

使用自己故意修改前两道答案的MyAnswers.txt文件

生成一万道题目


七、项目小结

  在这次四则运算自动生成项目中,我们通过分工合作,充分发挥各自的优势,实现了一个功能相对完整的工具。项目主要包括题目的生成、答案的计算与判断,以及结果的输出。
  这次的主要收获有几个方面。一是在开发过程中,我们学会了如何有效地分工与协作。通过定期沟通,确保了各模块之间的顺利对接,提升了项目效率。二是在实现过程中,我们个人的技术得到,对项目开发有更深刻的理解。三是提升问题解决能力,项目中遇到的各种问题,锻炼了我们的调试和解决问题的能力。通过查阅资料和互相讨论,我们逐步克服了困难。
  通过这个项目,我们不仅提升了技术能力,也增进了团队之间的默契,为今后更多的合作奠定了良好的基础。

posted @ 2024-09-24 21:15  孤舟一叶~  阅读(73)  评论(0编辑  收藏  举报