结对项目--小学四则运算题目生成器

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/homework/13479
这个作业的目标 实现四则运算题目生成、答案生成、判对错的需求,接触合作开发项目的流程
github地址 https://github.com/xingchen-boot/FourOpsQuiz
成员1:陈周裕 3123004784
成员2:林昭南 3123004795

一、 PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 25 25
· Estimate · 估计任务时间 25 30
Development 开发 480 500
· Analysis · 需求分析 60 50
· Design Spec · 生成设计文档 45 40
· Design Review · 设计复审 30 25
· Coding Standard · 代码规范制定 25 20
· Design · 具体设计 60 70
· Coding · 具体编码 250 300
· Code Review · 代码复审 40 50
· Test · 测试 29 30
Reporting 报告 90 100
· Test Report · 测试报告 25 35
· Size Measurement · 计算工作量 25 15
· Postmortem & Process Improvement Plan · 事后总结 40 50
合计 650 680

二、 效能分析

1. 性能优化时间

在性能优化上,我们花费了约120分钟,比预估的90分钟稍长。主要优化集中在表达式生成和计算部分。

2. 优化思路

(1) 表达式去重优化:使用HashSet存储已生成的表达式,快速检测重复,避免生成重复题目。

(2) 表达式计算优化:优化了中缀表达式的计算算法,减少了栈操作的次数。

(3) 分数运算优化:实现了高效的分数约分算法,使用最大公约数(GCD)算法简化分数计算。

(4) 文件IO优化:使用缓冲流(BufferedReader/BufferedWriter)代替普通的文件读写,提高了文件操作效率。

3. 性能分析图

性能分析结果(生成1000道题目):
- 表达式生成: 58% (主要消耗)
- 表达式计算: 32%
- 文件IO: 7%
- 其他操作: 3%

4. 消耗最大的函数

根据性能分析,消耗最大的函数是ExpressionGenerator类中的generateExpressions方法,该方法负责生成指定数量的唯一四则运算表达式。该函数消耗大的原因是需要频繁生成随机表达式并检查重复,同时还要确保表达式符合所有约束条件(无负数、除法为真分数等)。

三、 设计实现过程

1. 代码组织结构

com.calculator/
├── CalculatorApp.java      # 主应用程序类
├── ExpressionGenerator.java # 表达式生成器类
├── ExpressionEvaluator.java # 表达式计算类
├── Fraction.java           # 分数类
├── FileHandler.java        # 文件处理类
└── util/                   # 工具类
    └── MathUtils.java      # 数学工具类

2. 类关系图

CalculatorApp
     ↑
     ├── ExpressionGenerator ←→ Fraction
     ├── ExpressionEvaluator ←→ Fraction
     └── FileHandler

3. 主要类和函数说明

(1) CalculatorApp类

  • 主要功能:处理命令行参数,调用其他类完成题目生成和评分功能
  • 关键函数:main()generateExercises()gradeExercises()

(2) ExpressionGenerator类

  • 主要功能:生成四则运算表达式
  • 关键函数:generateExpressions()generateExpression()generateRandomNumber()

(3) ExpressionEvaluator类

  • 主要功能:计算表达式结果
  • 关键函数:evaluate()evaluateExpression()applyOperator()

(4) Fraction类

  • 主要功能:表示分数并提供分数运算
  • 关键函数:add()subtract()multiply()divide()reduce()

(5) FileHandler类

  • 主要功能:处理文件读写操作
  • 关键函数:writeExpressionsToFile()readExpressionsFromFile()writeGradeToFile()

4. 关键函数流程图

(1) 表达式生成流程 (ExpressionGenerator.generateExpressions())

flowchart TD A[开始] --> B[初始化计数器和表达式集合] B --> C{生成数量是否达标?} C -- 是 --> J[返回表达式列表] C -- 否 --> D[随机生成表达式] D --> E{是否重复?} E -- 是 --> D E -- 否 --> F[计算表达式结果] F --> G{结果是否有效?} G -- 否 --> D G -- 是 --> H[添加到结果列表] H --> I[增加计数器] I --> C J --> K[结束]

(2) 表达式计算流程 (ExpressionEvaluator.evaluateExpression())

flowchart TD A[开始] --> B[初始化数字栈和运算符栈] B --> C{遍历表达式字符} C -- 空格 --> D[跳过空格] C -- 左括号 --> E[将左括号入运算符栈] C -- 右括号 --> F[计算括号内表达式] F --> G[弹出左括号] C -- 运算符 --> H{处理运算符优先级} H --> I[将运算符入栈] C -- 数字 --> J[解析数字并入数字栈] D --> C E --> C G --> C I --> C J --> C C -- 遍历结束 --> K{运算符栈是否为空?} K -- 否 --> L[计算剩余运算] L --> K K -- 是 --> M[返回最终结果] M --> N[结束]

(3) 主程序流程 (CalculatorApp.main())

flowchart TD A[开始] --> B{解析命令行参数} B -- 评分模式 --> C[调用gradeExercises方法] B -- 生成题目模式 --> D[调用generateExercises方法] B -- 参数错误 --> E[打印帮助信息] C --> F[读取题目和答案文件] F --> G[评分并生成结果] D --> H[生成指定数量的表达式] H --> I[计算答案并写入文件] G --> J[结束] I --> J E --> J

(4) 分数约分流程 (Fraction.reduce())

flowchart TD A[开始] --> B[计算分子和分母的最大公约数] B --> C{最大公约数是否大于0?} C -- 是 --> D[分子除以最大公约数] D --> E[分母除以最大公约数] C -- 否 --> F[结束] E --> F

四、 代码说明

1. 表达式生成关键代码

/**
 * 生成指定数量的四则运算表达式
 * @param count 表达式数量
 * @return 表达式列表
 */
public List<String> generateExpressions(int count) {
    List<String> expressions = new ArrayList<>();
    int generatedCount = 0;
    
    // 循环生成表达式直到达到指定数量
    while (generatedCount < count) {
        // 随机决定表达式的复杂度(运算符数量)
        int operatorCount = random.nextInt(3) + 1; // 1-3个运算符
        
        // 生成表达式
        String expression = generateExpression(operatorCount);
        
        // 检查是否重复
        if (generatedExpressions.contains(expression)) {
            continue;
        }
        
        try {
            // 计算表达式结果以验证是否符合要求
            Fraction result = ExpressionEvaluator.evaluate(expression);
            
            // 确保结果非负
            if (result.isNegative()) {
                continue;
            }
            
            // 添加到结果列表
            expressions.add(expression);
            generatedExpressions.add(expression);
            generatedCount++;
        } catch (Exception e) {
            // 忽略无效表达式
            continue;
        }
    }
    
    return expressions;
}

2. 表达式计算关键代码

/**
 * 计算表达式的结果
 * @param expression 表达式字符串
 * @return 计算结果
 */
public static Fraction evaluate(String expression) {
    // 移除等号
    expression = expression.replace("=", "").trim();
    
    // 处理表达式计算
    return evaluateExpression(expression);
}

/**
 * 使用两个栈计算表达式
 */
private static Fraction evaluateExpression(String expression) {
    // 使用两个栈:一个存储数字,一个存储运算符
    Stack<Fraction> numbers = new Stack<>();
    Stack<Character> operators = new Stack<>();

    int i = 0;
    while (i < expression.length()) {
        char c = expression.charAt(i);
        
        // 跳过空格
        if (c == ' ') {
            i++;
            continue;
        }
        
        // 处理括号
        if (c == '(') {
            operators.push(c);
            i++;
        } else if (c == ')') {
            // 计算括号内的表达式
            while (!operators.isEmpty() && operators.peek() != '(') {
                numbers.push(applyOperator(operators.pop(), numbers.pop(), numbers.pop()));
            }
            operators.pop(); // 弹出左括号
            i++;
        } 
        // 处理运算符
        else if (isOperator(c)) {
            // 处理优先级
            while (!operators.isEmpty() && precedence(operators.peek()) >= precedence(c)) {
                numbers.push(applyOperator(operators.pop(), numbers.pop(), numbers.pop()));
            }
            operators.push(c);
            i++;
        } 
        // 处理数字(整数或分数)
        else {
            // 解析数字
            StringBuilder numBuilder = new StringBuilder();
            while (i < expression.length() && (Character.isDigit(expression.charAt(i)) || expression.charAt(i) == '/' || expression.charAt(i) == '\'')) {
                numBuilder.append(expression.charAt(i));
                i++;
            }
            String numStr = numBuilder.toString();
            numbers.push(Fraction.parseFraction(numStr));
        }
    }
    
    // 处理剩余的运算符
    while (!operators.isEmpty()) {
        numbers.push(applyOperator(operators.pop(), numbers.pop(), numbers.pop()));
    }
    
    // 返回最终结果
    return numbers.pop();
}

3. 分数运算关键代码

/**
 * 分数类,用于表示分数并提供分数运算
 */
public class Fraction {
    private long numerator;   // 分子
    private long denominator; // 分母
    
    // 构造函数
    public Fraction(long numerator, long denominator) {
        if (denominator == 0) {
            throw new IllegalArgumentException("分母不能为0");
        }
        
        // 确保分母为正数
        if (denominator < 0) {
            numerator = -numerator;
            denominator = -denominator;
        }
        
        this.numerator = numerator;
        this.denominator = denominator;
        
        // 约分
        reduce();
    }
    
    /**
     * 加法运算
     */
    public Fraction add(Fraction other) {
        long newNumerator = this.numerator * other.denominator + other.numerator * this.denominator;
        long newDenominator = this.denominator * other.denominator;
        return new Fraction(newNumerator, newDenominator);
    }
    
    /**
     * 减法运算
     */
    public Fraction subtract(Fraction other) {
        long newNumerator = this.numerator * other.denominator - other.numerator * this.denominator;
        long newDenominator = this.denominator * other.denominator;
        return new Fraction(newNumerator, newDenominator);
    }
    
    /**
     * 乘法运算
     */
    public Fraction multiply(Fraction other) {
        long newNumerator = this.numerator * other.numerator;
        long newDenominator = this.denominator * other.denominator;
        return new Fraction(newNumerator, newDenominator);
    }
    
    /**
     * 除法运算
     */
    public Fraction divide(Fraction other) {
        if (other.numerator == 0) {
            throw new ArithmeticException("除数不能为0");
        }
        long newNumerator = this.numerator * other.denominator;
        long newDenominator = this.denominator * other.numerator;
        return new Fraction(newNumerator, newDenominator);
    }
    
    /**
     * 约分
     */
    private void reduce() {
        long gcd = MathUtils.gcd(Math.abs(numerator), denominator);
        if (gcd > 0) {
            numerator /= gcd;
            denominator /= gcd;
        }
    }
    
    /**
     * 转换为字符串表示
     */
    @Override
    public String toString() {
        // 处理整数情况
        if (denominator == 1) {
            return String.valueOf(numerator);
        }
        
        // 处理带分数情况
        if (Math.abs(numerator) > denominator) {
            long integerPart = numerator / denominator;
            long fractionalNumerator = Math.abs(numerator % denominator);
            return integerPart + "'" + fractionalNumerator + "/" + denominator;
        }
        
        // 普通分数情况
        return numerator + "/" + denominator;
    }
}

五、 测试运行

1. 命令行测试

(1)题目生成
指令:java com.calculator.CalculatorApp -n 10 -r 10
运行结果:
577043efb57a6cec71f4faa5ce51d6f
2c6a29a04f034e9790b2262cbd977bd
4063b9c668fcfda77004051741a130e

(2)题目评分
指令:java com.calculator.calculatorApp -e Exercises.txt -a Answers.txt
运行结果:
2c09f2ef5cc27f48b55cf175de1103d
575795ce2ec53f44c74397cdc330481

2. 图形化界面测试

(1)题目生成:
7adf0106c5f55b1db3ddedee21d8d1e
2c5de80a13d71cd21cea1ac1c0672a8
abcdbc1229f701ed671da8e73334244

txt文件如下:
ad2db737b7d52a4d740f7842056a232
1f576a826a08e83ad7ae4eebad7ddc4

(2)题目评分:
0bd458952ba99e07aaaa558cf64b8eb
9e8bda763e869bbfd85906d86e43916
d69043119686725ce7f6c9b369c1812

txt文件如下:
3f7a449a55644608d70a93aa86fbc49

六、 项目小结

1. 结对开发感受

通过这次结对项目,我们深刻体会到了团队协作的重要性。结对编程不仅提高了代码质量,还加快了问题解决的速度。在遇到困难时,两个人可以从不同角度思考问题,往往能更快找到解决方案。同时,代码审查过程也帮助我们发现了许多自己可能忽略的问题,提高了代码的健壮性。

2. 经验教训

(1) 时间管理:实际开发时间比预估长了约11.7%,主要是在核心功能实现和测试阶段花费了更多时间。下次项目中需要更准确地评估任务复杂度。

(2) 需求理解:在开发初期,我们对某些需求的理解不够深入,导致后期需要调整。应该在开始编码前确保完全理解所有需求。

(3) 代码复用:在开发过程中,我们发现有些功能可以进一步抽象和复用,减少了重复代码。良好的代码组织和模块化设计非常重要。

(4) 测试驱动开发:采用测试驱动开发可以更早地发现问题,减少后期调试的时间。

3. 后续改进方向

(1) 添加更多题型支持,如填空题、选择题等

(2) 实现更智能的题目难度分级系统

(3) 增加用户账号管理和成绩统计功能

(4) 优化图形界面,提供更好的用户体验

(5) 添加数据持久化,支持题目和成绩的存储与查询

总的来说,这次结对项目是一次非常有价值的经验,不仅提高了我们的编程能力,还培养了团队协作精神。我们相信,这些经验将对未来的学习和工作产生积极影响。

posted @ 2025-10-20 18:27  ironlifes  阅读(9)  评论(0)    收藏  举报