结对项目
第三周作业
| 这个作业属于哪个课程 | 计科24级56班(广东工业大学 - 计算机学院)软件工程 |
|---|---|
| 这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Class56-Grade2024-CS/homework/15694 |
| 这个作业的目标 | 实现一个自动生成小学四则运算题目的命令行程序 |
| 杨富林 | 3124004072 |
| 黎正阳 | 3124004060 |
| Github 项目地址 | https://github.com/xiersg/four-arithmetic-generator |
1. PSP 表格
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 20 | 15 |
| · Estimate | · 估计这个任务需要多少时间 | 15 | 10 |
| Development | 开发 | ||
| · Analysis | · 需求分析(包括学习新技术) | 40 | 35 |
| · Design Spec | · 生成设计文档 | 25 | 20 |
| · Design Review | · 设计复审 | 15 | 10 |
| · Coding Standard | · 代码规范 | 15 | 15 |
| · Design | · 具体设计 | 40 | 35 |
| · Coding | · 具体编码 | 180 | 160 |
| · Code Review | · 代码复审 | 40 | 30 |
| · Test | · 测试(自我测试,修改代码,提交修改) | 60 | 50 |
| Reporting | 报告 | ||
| · Test Report | · 测试报告 | 30 | 25 |
| · Size Measurement | · 计算工作量 | 15 | 10 |
| · Postmortem & Process Improvement Plan | · 事后总结并提出过程改进计划 | 25 | 20 |
| 合计 | 520 | 435 |
开始编码前先填「预估」列;做完后再填「实际」列。
2. 效能分析
生成题目时最耗时的是「构造表达式树 + 约束校验 + 规范化去重」。第一次实现时每次生成都重新求值失败就整棵扔掉,一万道题在较小 -r 时重试次数偏高。
改进思路:
- 在构造减法、除法节点时就地调整左右子树,减少整树作废。
- 用规范化元组(而不是字符串正则)做去重,比较更快、也更符合交换律定义。
- 文件写入一次拼好再写,避免逐行频繁打开文件。
实测:只生成 10000 道(-r 20)约 0.9 秒;连同写成 Exercises.txt / Answers.txt 约 1–2 秒。cProfile 显示热点在 _build_tree 随机构造与 evaluate 约束检查,文件 I/O 不是主因。
在项目根目录可自行复现:
python -m cProfile -s tottime -m arithmetic -n 10000 -r 20 --seed 1
把运行截图(函数耗时表)贴到博客「效能分析」一节。改进前后可对比「生成 10000 题的墙钟时间」。
3. 设计实现过程
程序按职责拆成 5 个模块:
| 模块 | 职责 |
|---|---|
Frac |
约分、带分数格式、四则运算 |
Num / Op |
二叉树表示题目;求值时检查「中间结果非负、除法得真分数」 |
canonical() |
对 +、× 排序左右子树,识别交换律重复 |
generate_exercises() |
随机 1~3 个运算符,集合去重后写文件 |
write_grade() |
解析题目求值,与答案文件逐题比较 |
关键流程(出题):
- 读入
-n、-r(没有-r则报错退出)。 - 随机决定运算符个数 k∈[1,3],递归生成左右子树。
- 求值;失败则重试。成功则计算 canonical key,已存在则重试。
- 写
Exercises.txt与Answers.txt。
批改:解析 e = 表达式 → 精确分数比较 → 按题号输出 Correct/Wrong。
4. 代码说明
去重是本题最容易丢分的点。1 + 2 + 3 因左结合等于 (1+2)+3,交换后得到 3+(1+2),再交换内层得到 3+(2+1),故与 3+(2+1) 重复;但 3+2+1 是 (3+2)+1,不能只靠交换变成 (1+2)+3。
规范化代码见 arithmetic/expression.py 中 canonical():只对 +、* 交换左右孩子,不改变结合方向。
除法约束见 evaluate():结果必须是真分数(0 < |值| < 1),否则丢弃该题。
5. 测试运行
至少 10 个用例(python -m unittest discover -s tests -v):
1/6 + 1/8 = 7/24- 带分数格式
5/3 → 1'2/3,并能解析1’1/2 is_proper():1/2真,3/2、整数、0 非真- 减法过程出现负数则非法
6 ÷ 3非法(结果不是真分数),1 ÷ 2合法23+45与45+23判定为重复6×8与8×6判定为重复1+2+3与3+(2+1)判定为重复1+2+3与3+2+1判定为不重复- 运算符优先级:
1 + 2 × 3 = 7 - 生成 20 道题:运算符个数 ∈[1,3],答案与求值一致
- 按作业示例批改:Correct: 5 (1, 3, 5, 7, 9) / Wrong: 5 (2, 4, 6, 8, 10)
- 缺
-r时返回错误并打印帮助 - 生成后用标准答案批改,应得全对
这些用例覆盖了格式、约束、去重、命令行和批改,因此可以确定程序按作业要求工作。手工再跑一遍:
python myapp.py -n 10 -r 10
python myapp.py -e Exercises.txt -a Answers.txt
6. 项目小结
两人一起完成需求拆分、接口约定和互相 review。建议在博客里各写一段:对方的闪光点、本次合作的得失、下次如何改进(例如先写测试再写生成器、更早用 10000 题压测去重性能)。
可直接使用的句子模板:
- 杨富林:在分数约分和去重定义上把作业原文抠得很细,避免了「看起来能算但对不上样例」的情况。
- 黎正阳:把模块边界划清楚,CLI / 生成 / 批改可以分开测,后面补 GUI 没有改核心逻辑。
教训:交换律去重要用树结构,不能只把字符串排序;1+2+3 与 3+2+1 是作业里专门点名的反例。
浙公网安备 33010602011771号