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

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

项目成员:罗晟 3124004103、梁译航 3124004101
GitHub 地址:https://github.com/3124004103ls/3124004103ls


一、PSP 表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 30 45
· Estimate · 估计这个任务需要多少时间 30 45
Development 开发 475 540
· Analysis · 需求分析(包括学习新技术) 45 60
· Design Spec · 生成设计文档 30 45
· Design Review · 设计复审 20 30
· Coding Standard · 代码规范 20 30
· Design · 具体设计 60 90
· Coding · 具体编码 180 120
· Code Review · 代码复审 30 45
· Test · 测试 90 120
Reporting 报告 80 120
· Test Report · 测试报告 30 45
· Size Measurement · 计算工作量 20 30
· Postmortem & Process Improvement Plan · 事后总结,并提出过程改进计划 30 45
合计 585 705

二、效能分析

1. 性能测试结果

用 PerformanceTest 测试 10000 道题的生成、计算、规范化耗时:

测试 10000 道题耗时
生成 16 ms
计算 2 ms
规范化 5 ms

结论:性能瓶颈在生成环节,因为生成时要反复构建表达式树、检查合法性、去重。

2. 性能改进

改进点:ExpressionGenerator.generate() 里,把合法性检查和重复检查合并成一次 if,减少 continue 次数:

// 改进前:两个 continue
if (!isValid(expr)) continue;
if (seen.contains(key)) continue;

// 改进后:一个 continue
if (!isValid(expr) || seen.contains(key)) continue;

改进效果:生成 10000 道题耗时稳定在 16ms 左右。


三、设计实现过程

1. 代码组织

类 职责
Main 程序入口,解析命令行参数
Fraction 分数类,支持加减乘除、化简、比较
Expression 表达式树节点
ExpressionGenerator 题目生成器
ExpressionNormalizer 去重
ExpressionParser 字符串转表达式树
ExpressionPrinter 表达式树转字符串
FileIO 文件读写
Grader 判分器

2. 类之间的关系

Main
├── ExpressionGenerator
│   ├── Expression
│   ├── Fraction
│   └── ExpressionNormalizer
├── FileIO
│   ├── ExpressionPrinter
│   └── Expression
└── Grader
    ├── ExpressionParser
    ├── Expression
    └── Fraction

3. 关键算法

题目生成:

  1. 随机决定运算符个数(1-3 个)
  2. 递归构建表达式树
  3. 检查合法性:
    • 减法:左 ≥ 右(不能出现负数)
    • 除法:除数 ≠ 0,结果必须是真分数
    • 乘法:两个数都不能是 0
  4. 检查重复:用 ExpressionNormalizer 规范化后查 HashSet
  5. 合法且不重复则返回,否则重新生成

去重算法:

  • 加法、乘法满足交换律和结合律:a + b 和 b + a 视为相同,a + (b + c) 和 (a + b) + c 展平后排序
  • 减法、除法不满足交换律,顺序固定
  • 用规范化后的字符串作为唯一标识

四、代码说明

1. Fraction 核心方法

public Fraction add(Fraction other) {
    int num = this.numerator * other.denominator + other.numerator * this.denominator;
    int den = this.denominator * other.denominator;
    return new Fraction(num, den);
}

分数加法:通分后相加,构造函数里自动化简。

2. ExpressionGenerator 合法性检查

private boolean isValid(Expression expr) {
    if (expr.isNumber()) return true;
    if (!isValid(expr.getLeft()) || !isValid(expr.getRight())) return false;
    Fraction l = expr.getLeft().evaluate();
    Fraction r = expr.getRight().evaluate();

    switch (expr.getType()) {
        case MULTIPLY:
            if (l.getNumerator() == 0 || r.getNumerator() == 0) return false;
            break;
        case DIVIDE:
            if (r.getNumerator() == 0) return false;
            if (l.getNumerator() == 0) return false;
            Fraction result = l.divide(r);
            if (result.getNumerator() >= result.getDenominator()) return false;
            break;
        case SUBTRACT:
            if (l.compareTo(r) < 0) return false;
            break;
        default:
            break;
    }
    return true;
}

3. ExpressionNormalizer 去重

private static String normalizeAssociative(String op, String left, String right) {
    List<String> operands = new ArrayList<>();
    collectOperands(op, left, operands);
    collectOperands(op, right, operands);
    operands.sort(String::compareTo);
    return "(" + String.join(op, operands) + ")";
}

把加法和乘法的操作数收集起来,排序后拼接,保证 23 + 45 和 45 + 23 规范化后相同。


五、测试运行

1. 测试用例

测试类 测试数 内容
FractionTest 8 加减乘除、化简、toString、compareTo
ExpressionGeneratorTest 3 生成、不重复、无负数
GraderTest 2 全部正确、部分正确
PerformanceTest 3 生成、计算、规范化性能
合计 16

2. 测试结果

Tests run: 16, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESS

3. 测试说明

  • FractionTest:验证分数运算的正确性,覆盖化简、带分数转换等边界情况
  • ExpressionGeneratorTest:验证生成的题目不重复、无负数、合法性
  • GraderTest:验证判分器能正确区分对错
  • PerformanceTest:验证性能满足 10000 道题的要求

六、Code Quality Analysis

使用 SonarQube for IDE 对项目做代码质量分析。

主要修复:

规则 说明
S106 System.out 改用 Logger
S1118 工具类加私有构造函数
S2629 LOGGER.info 用占位符
S5786 测试类去掉多余 public
S5976 合并类似测试为参数化

最终状态:No SonarQube issues to display.


七、项目小结

结对感受

合作顺利的地方:

  • 两人对需求理解基本一致,算法设计阶段沟通顺畅。
  • 代码复审时互相发现了几个边界问题(如 0 参与乘除法、除法结果是整数等)。

遇到的困难:

  • 中文编码问题:之前设置了打开的文件自动以 GBK 形式保存,导致“×”,“÷” 以及中文显示乱码,哪怕使用Unicode转义字符都没用。在这一部分浪费了很多时间,最终没办法了,只能改用 “*”,“/” 替代,特此说明。
    b25ed9e588f7b1aafc4d5d91b896e411
    然后“÷”改成“/”后,程序识别不了类似9 / 1/3(正常应该是9÷1/3)这个式子导致答案出错,这个问题目前我们还没解决,所以最后判断正误还有3.7%的错误率。
  • 去重算法:一开始没考虑结合律,后来改用「展平 + 排序」的方式解决。

罗晟心得体会:在AI的辅助下终于艰难的完成了这个项目的大部分。由于基础薄弱,导致即便有AI帮忙写代码有几行还是不懂就像System.out在SonarQube里为什么要改成LOGGER才能消除警告,而且调试的时候出错还要在Ai的帮助下才能弄懂这里为什么会错,以及要怎么修改,甚至做到最后生成10000题后判断正误,错误率还是高达3.7%。如果一开始“×”,“÷” 如果没出错我应该能省下挺多时间的。
2b3189a7b5c6bc31f3331c4a5a7ad6e1

梁译航心得体会:通过这次结对,我意识到测试并不是简单地跑一遍程序,而是要主动去构造一些“刁钻”的输入,比如0参与乘法、除法结果是整数这些情况,才能真正发现代码里的漏洞。虽然最后判分还有3.7%的误差没有彻底解决,但整体上我们两个人配合得还不错,他负责算法和核心逻辑,我负责测试和复审,互相补充了各自的短板。


八、附录

运行示例

生成 10 道题:

java -jar main.jar -n 10 -r 10

生成 10000 道题:

java -jar main.jar -n 10000 -r 10

判分:

java -jar main.jar -e Exercises.txt -a Answers.txt

输出文件示例

Exercises.txt:

1. 1/2 * 5/7 =
2. 3/7 * 8/9 * (9 * 3) =
...

Answers.txt:

1. 1/2 * 5/7 = 5/14
2. 3/7 * 8/9 * (9 * 3) = 72/7
...

Grade.txt:

Correct: 10 (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
Wrong: 0 ()
posted @ 2026-09-19 22:13  luosheng24  阅读(17)  评论(0)    收藏  举报