软件工程第三次作业——结对项目
| 这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience |
|---|---|
| 这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience/homework/13470 |
| 这个作业的目标 | 结队项目:开发自动生成小学四则运算题目的命令行程序 |
一、个人信息
| 姓名 | 学号 |
|---|---|
| 黄怀瑾 | 3223004381 |
| 林欣(其他班的同学) | 3223004382 |
GitHub地址:https://github.com/baiyehhj/baiyehhj/tree/main/3223004381/第三次软件工程作业
二、PSP表格
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 20 | 20 |
| · Estimate | · 估计这个任务需要多少时间 | 20 | 20 |
| Development | 开发 | 520 | 545 |
| · Analysis | · 需求分析 (包括学习新技术) | 60 | 70 |
| · Design Spec | · 生成设计文档 | 10 | 10 |
| · Design Review | · 设计复审 (和同事审核设计文档) | 10 | 15 |
| · Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 5 |
| · Design | · 具体设计 | 30 | 40 |
| · Coding | · 具体编码 | 250 | 230 |
| · Code Review | · 代码复审 | 30 | 20 |
| · Test | · 测试(自我测试,修改代码,提交修改) | 120 | 150 |
| Reporting | 报告 | 90 | 95 |
| · Test Report | · 测试报告 | 60 | 60 |
| · Size Measurement | · 计算工作量 | 15 | 15 |
| · Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 15 | 20 |
| 合计 | 630 | 660 |
三、效能分析


改进程序性能花费的时间:
| 优化模块 | 花费时间 | 优化详情 |
|---|---|---|
| 表达式树生成优化 | 60分钟 | 递归限制 + 缓存策略 + 平衡优化 |
| 去重算法优化 | 45分钟 | 标准化算法 + 哈希优化 + 缓存机制 |
| 分数运算优化 | 45分钟 | 约分算法 + 运算缓存 + 内存复用 |
改进思路:
- 表达式树生成优化思路:
递归控制:通过深度限制和尝试次数上限防止无限递归,设置降级策略确保生成可靠性
缓存机制:对运算合法性验证结果进行LRU缓存,避免重复验证相同表达式结构
平衡分配:优化运算符分配算法,避免生成过于不平衡的树结构,提高生成效率 - 去重算法优化思路:
预计算缓存:将标准化结果预先计算并缓存,避免重复的标准化计算开销
哈希优化:缓存哈希值减少重复计算,提高集合操作性能
分层过滤:采用快速特征过滤先行排除明显不同的表达式,减少完整标准化比较次数 - 分数运算优化思路:
算法优化:使用GCD计算缓存和延迟约分策略,减少不必要的计算开销
结果缓存:预缓存常见运算结果,利用空间换时间提升高频操作性能
资源复用:引入对象池机制减少内存分配和垃圾回收压力,提升内存使用效率
消耗最大的函数:
ExpressionTree.generateTree()
主要消耗原因:深度递归 + 重复计算 + 合法性验证
四、设计实现过程
1.类结构设计
① ArithmeticGenerator(算术生成器类)
- 职责:协调整个题目生成流程
- 主要函数:
main()- 程序入口,参数解析
generateExercises()- 生成题目主流程
checkAnswers()- 答案验证功能
② ExpressionTree(表达式树类)
- 职责:数学表达式的树形表示和核心操作
- 主要函数:
generateTree()- 递归生成表达式树(核心算法)
calculate()- 计算表达式值
normalize()- 标准化表达式(去重关键)
toExpression()- 转换为字符串表达式
isOperationValid()- 验证运算合法性
③ Fraction(分数类)
- 职责:精确的分数运算和处理
- 主要函数:
add()/subtract()/multiply()/divide()- 四则运算
parseFraction()- 字符串解析为分数
gcd()- 最大公约数计算(约分核心)
toString()- 分数格式化输出
④ ExpressionUtils(表达式工具类)
- 职责:表达式相关的工具函数
- 主要函数:
generateNumber()- 生成随机数(自然数/分数)
calculateExpression()- 表达式计算入口
buildExpressionTree()- 构建表达式树
isProperDivision()- 验证除法合法性
⑤ FileUtils(文件工具类)
- 职责:所有文件读写操作
- 主要函数:
writeToFile()- 写入题目/答案文件
readFromFile()- 读取文件内容
formatNumbers()- 格式化输出
2.类关系设计
┌──────────────────────┐ ┌────────────────────────┐ ┌──────────────────┐
│ ArithmeticGenerator │──────│ ExpressionUtils │──────│ ExpressionTree │
├──────────────────────┤ ├────────────────────────┤ ├──────────────────┤
│ - main() │ │ - generateNumber() │ │ - value │
│ - generateExercises()│ │ - parseFraction() │ │ - left ◄─────────┘
│ - checkAnswers() │ │ - calculateExpression()│ │ - right ◄────────┘
└──────────────────────┘ └────────────────────────┘ └──────────────────┘
│ │ │
│ │ │
▼ ▼ ▼
┌──────────────────┐ ┌──────────────────┐ (递归自引用)
│ FileUtils │ │ Fraction │ ExpressionTree
├──────────────────┤ ├──────────────────┤ │
│ - writeToFile() │ │ - numerator │ 左子树│右子树
│ - readFromFile() │ │ - denominator │ │ │
│ - formatNumbers()│ │ - add() │ ExpressionTree ...
└──────────────────┘ │ - subtract() │
│ - multiply() │
│ - divide() │
└──────────────────┘
3.关键函数流程图
3.1表达式生成流程
开始
↓
generateTree(maxOperators, range)
↓
maxOperators <= 0? ──是──→ 生成数字节点
↓否
随机选择运算符 (+, -, ×, ÷)
↓
分配左右子树运算符数量
↓
递归生成左子树
↓
递归生成右子树
↓
检查运算合法性
↓不合法 ┌─────────────┐
└─── 重新生成 ←────────┘
↓合法
创建表达式树节点
↓
返回表达式树
3.2表达式计算流程
开始
↓
calculate()
↓
是叶子节点? ──是──→ 返回节点值
↓否
递归计算左子树
↓
递归计算右子树
↓
解析为分数对象
↓
根据运算符计算:
+ → Fraction.add()
- → Fraction.subtract()
× → Fraction.multiply()
÷ → Fraction.divide()
↓
返回结果字符串
五、代码说明
1.表达式树生成
/**
* 生成表达式树 - 核心算法
* 使用递归方式构建二叉树结构,确保运算优先级和合法性
*/
public static ExpressionTree generateTree(int maxOperators, int range) {
// 基础情况:生成操作数(自然数或分数)
if (maxOperators <= 0) {
return new ExpressionTree(ExpressionUtils.generateNumber(range));
}
// 随机选择运算符,增加题目多样性
char[] operators = {'+', '-', '*', '/'};
char op = operators[random.nextInt(operators.length)];
// 智能分配运算符数量,确保树结构平衡
int leftOps = random.nextInt(maxOperators);
int rightOps = maxOperators - 1 - leftOps;
// 递归构建左右子树
ExpressionTree leftTree = generateTree(leftOps, range);
ExpressionTree rightTree = generateTree(rightOps, range);
// 验证运算约束:减法结果非负,除法结果为真分数
if (!isOperationValid(op, leftTree, rightTree)) {
return generateTree(maxOperators, range); // 重新生成
}
return new ExpressionTree(String.valueOf(op), leftTree, rightTree);
}
2.分数运算核心
/**
* 分数加法运算
* 采用通分后相加的数学原理:a/b + c/d = (a*d + c*b)/(b*d)
*/
public Fraction add(Fraction other) {
int newNumerator = this.numerator * other.denominator +
this.denominator * other.numerator;
int newDenominator = this.denominator * other.denominator;
return new Fraction(newNumerator, newDenominator); // 自动约分
}
/**
* 分数乘法运算
* 分子乘分子,分母乘分母,然后约分
*/
public Fraction multiply(Fraction other) {
int newNumerator = this.numerator * other.numerator;
int newDenominator = this.denominator * other.denominator;
return new Fraction(newNumerator, newDenominator);
}
3.表达式标准化去重
/**
* 表达式标准化 - 用于题目去重
* 通过交换律处理,确保数学等价的表达式具有相同的规范形式
*/
public String normalize() {
if (isLeaf()) {
return value;
}
// 递归标准化子树
ExpressionTree normalized = normalizeTree();
// 转换为规范形式字符串用于去重比较
return normalized.toCanonicalForm();
}
private ExpressionTree normalizeTree() {
if (isLeaf()) {
return this;
}
ExpressionTree normLeft = left.normalizeTree();
ExpressionTree normRight = right.normalizeTree();
// 处理交换律:对可交换运算符排序操作数
if (isCommutative()) {
// 通过比较确保规范顺序(如确保 2+3 和 3+2 都规范为 2+3)
if (normLeft.compareTo(normRight) > 0) {
return new ExpressionTree(value, normRight, normLeft);
}
}
return new ExpressionTree(value, normLeft, normRight);
}
六、测试运行
1.测试用例说明:
- testMissingRequiredParameters():测试缺少必需参数时的程序行为,验证参数验证机制
- testNumberRangeControl():验证生成的数字在指定范围内,包括自然数和分数分母的范围控制
- testSubtractionNonNegative():测试减法运算结果非负的业务规则,确保不生成结果为负的表达式
- testDivisionResultProperFraction():验证除法运算结果为真分数的业务规则,确保除法运算符合要求
- testOperatorCountLimit():测试运算符数量不超过3个的限制,验证题目复杂度控制
- testNoDuplicateExpressions():验证题目去重机制,确保不生成重复的数学表达式
- testCommutativeLawDeduplication():测试交换律和结合律的去重处理,验证数学等价表达式的识别
- testFileFormatCorrectness():验证题目和答案文件的格式正确性,包括编号和格式规范
- testFractionFormatAndOperations():测试分数格式和四则运算的正确性,包括真分数和带分数
- testAnswerValidationFunction():测试答案验证功能的完整性,包括成绩统计和文件输出
- testLargeScaleGeneration():测试大规模题目生成的性能和正确性,验证系统稳定性
- testParenthesesHandling():测试括号表达式的计算正确性,验证运算优先级处理
- testExpressionTreeConstruction():验证表达式树的构建和计算功能,测试核心数据结构
- testEdgeCases():测试边界情况处理,包括最小范围和无效参数的处理
- testMixedFractionAndIntegerOperations():测试分数与整数混合运算的正确性,验证复杂表达式计算
- testDeduplicationEdgeCases():测试去重机制的边界情况,包括带括号表达式的交换律处理
- testFileIOErrorHandling():测试文件读写异常处理,验证系统的健壮性
- testCompleteWorkflow():集成测试完整流程,从生成到验证的全流程验证
- testFractionSimplification():测试分数化简功能,验证分数的最简形式输出
2.测试用例运行结果:

3.测试用例覆盖率:

七、项目小结
1.项目总结
本项目实现了小学四则运算题目生成与自动批改功能,成功完成题目生成、答案校验及成绩统计。通过表达式树实现题目去重(考虑交换律、结合律),并通过单元测试验证了数值范围、运算合法性(如减法非负、除法结果为真分数)等核心需求。
核心技术:
① 自动题目生成
- 精准参数控制:通过-n参数实现题目数量的精确控制(支持10,000道题目批量生成),-r参数确保数值范围严格符合小学数学教学要求
- 多元表达式支持:无缝集成自然数、真分数、带分数的混合运算,严格遵循真分数表示规范(如3/5、2'3/8)
- 复杂度智能管控:通过运算符计数算法确保每道题目运算符不超过3个,维持题目难度在合理区间
② 高级去重机制
- 表达式树架构:采用二叉树结构天然表达运算优先级和括号关系
- 规范化处理:实现基于交换律(+、×)和结合律的表达式标准化算法
- 深度去重:成功识别并过滤如"23+45"与"45+23"、"3+(2+1)"与"1+2+3"等语义重复题目
- 哈希优化:通过规范化表达式的哈希比较,实现高效的大规模题目去重
③ 运算合法性验证体系
- 减法非负保障:在表达式树每个减法节点进行递归验证,确保e1 ≥ e2,杜绝任何负数结果
- 除法真分数约束:严格验证每个除法运算结果满足分子小于分母的正分数条件
- 实时合法性检查:在题目生成阶段即进行预防性验证,避免无效题目的产生
④ 健壮的批改统计系统
- 文件智能解析:准确解析Exercises.txt和Answers.txt文件格式
- 答案精准比对:采用分数等价性验证而非字符串匹配,确保批改准确性
- 详细成绩报告:生成结构化的Grade.txt,清晰展示正确/错误题目数量及具体编号
2.结对感受
| 成员 | 分工范围 |
|---|---|
| 黄怀瑾 | 代码编写、核心算法、逻辑架构 |
| 林欣 | 基础组件、系统集成、测试用例 |
在结对项目中,我们采用了基于技术特长的垂直分工和关键节点的交叉评审相结合的合作模式,既发挥了各自的技术优势,又确保了代码质量的一致性。黄怀瑾主要负责主控流程设计、表达式树核心算法、题目去重系统,林欣主要负责文件操作与IO管理、设计测试用例。在每天开始编写代码前,我们会同步前日进展和当前问题,并在完成一个功能模块后进行交叉审查,确保代码风格和设计模式的一致性。
通过结对项目,我们深刻体会到"1+1>2"的协作力量。黄怀瑾在表达式树算法设计上展现了出色的架构能力,其清晰的逻辑思维让复杂问题简单化;林欣在测试用例设计的严谨性和文件处理的健壮性方面表现卓越,对边界情况考虑周全确保了系统的稳定性。我们不仅共同攻克了技术难题,更在代码规范、测试驱动开发等方面相互促进,实现了能力提升,收获了宝贵的协作项目经验。
浙公网安备 33010602011771号