结对项目

这个作业属于哪个课程 软件工程
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class78-Grade2024-CS/homework/15703
这个作业的目标 实现一个自动生成小学四则运算题目的命令行程序

小组成员:吴圣林 3124004485 林锶堉3124004476
作业 GitHub 地址:https://github.com/morganalter/arithmetic-exercise-generator

一、动手之前的 PSP 预估

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

二、效能分析
2.1 性能优化耗时
本次程序性能优化共花费 40 分钟。

2.2 性能改进思路
最初版本的程序存在 3 个明显的性能问题,我们针对性做了三轮优化:

  1. 约束校验前置,减少无效递归
    • 初始问题:先生成完整的表达式树,再整体校验减法非负、除法结果为分数的约束,不合格就整棵树直接丢弃,浪费了大量计算资源。
    • 优化方案:改为递归生成子树后立即校验,只要左 / 右子树不满足约束,直接返回None,上层不再继续生成另一半子树,大幅减少无效的递归和计算。
  2. 集合去重替代列表遍历
    • 初始问题:用普通列表存储已生成的题目,每次判重都要遍历整个列表,时间复杂度为 O (n),题目数量越大速度越慢。
    • 优化方案:改用 规范前缀串 + Python 集合(set)去重,集合的查找时间复杂度为 O (1)。
  3. 标准库替代手动分数运算
    • 初始问题:手动编写分数的通分、约分、四则运算代码,不仅冗余,而且纯 Python 实现的运算效率低。
    • 优化方案:直接使用 Python 内置的fractions.Fraction标准库,运算效率远高于手写 Python 代码,同时自动保证计算精度。

2.3性能测试与分析图
我们使用 Python 官方性能分析工具cProfile对程序进行采样,测试条件:生成 10000 道题目,参数为 -n 10000 -r 20。
测试总结果:程序共执行 267 万次函数调用,总运行时间仅0.778 秒。

我们使用snakeviz工具生成了可视化的调用栈冰柱图:
image

如图为优化后程序性能分析冰柱图,可以看到:

  1. 最顶层蓝色条是程序总入口,总耗时 0.778 秒;
  2. 橙色的generate_exercises是题目生成主函数,占据了绝大多数运行时间;
  3. 最宽的红色条带是_gen_expr(表达式递归生成函数),是整个程序中耗时占比最高的业务函数。

2.4 耗时最高的函数分析
image

如图,程序中消耗最大的函数:_gen_expr(表达式递归生成函数)

三.设计实现过程
本程序采用模块化设计,整体分为 3 大模块、1 个核心数据类。

3.1核心数据结构

采用二叉表达式树存储算术表达式:

  • 叶子节点:存储数值,类型为Fraction,统一表示自然数、真分数、带分数
  • 内部节点:存储运算符(+、−、×、÷)以及左右子树指针

3.2题目生成主流程

  1. 解析命令行参数,校验参数合法性
  2. 随机选择当前题目的运算符数量
  3. 递归生成表达式树,同步校验约束条件
  4. 生成规范串,通过集合判重,重复则重新生成
  5. 转换为带括号的中缀字符串,计算答案
  6. 达到指定数量后,分别写入 Exercises.txt 和 Answers.txt

四.代码说明
4.1表达式树节点类
统一表示表达式的叶子和内部节点,通过is_leaf方法区分节点类型。

class ExprNode:
def init(self, value=None, op=None, left=None, right=None):
self.value = value # 叶子节点存数值(Fraction)
self.op = op # 内部节点存运算符
self.left = left # 左子树
self.right = right # 右子树

def is_leaf(self):
return self.value is not None

4.2递归生成表达式树

核心生成函数,通过递归拆分运算符数量,生成后立即校验约束,不满足则返回 None 由上层重试,保证生成的题目全部符合规则。

def _gen_expr(op_cnt, r):
if op_cnt == 0:
return gen_number(r)

ops = ['+', '-', '×', '÷']
op = random.choice(ops)
# 随机分配运算符数量到左右子树
left_ops = random.randint(0, op_cnt - 1)
right_ops = op_cnt - 1 - left_ops

left = _gen_expr(left_ops, r)
right = _gen_expr(right_ops, r)
if left is None or right is None:
return None

l_val = evaluate(left)
r_val = evaluate(right)
# 校验减法非负
if op == '-':
if l_val < r_val:
return None
# 校验除法结果为分数
elif op == '÷':
if r_val == 0:
return None
res = l_val / r_val
if res.denominator == 1:
return None
return ExprNode(op=op, left=left, right=right)

4.3规范串生成

后序遍历生成前缀表达式,对交换律运算符按字典序排序子节点,确保等价题目生成相同字符串,是去重功能的核心逻辑。

def get_canonical(node):
if node.is_leaf():
return format_fraction(node.value)
l = get_canonical(node.left)
r = get_canonical(node.right)
# +和×交换左右序,生成唯一规范串
if node.op in ('+', '×'):
if l > r:
l, r = r, l
return f"({node.op} {l} {r})"

4.4题目批改核心
逐题读取题目和用户答案,分别计算正确值并比对,统计对错题号,最终按要求格式输出批改结果。

def grade(exercise_file, answer_file):

with open(exercise_file, 'r', encoding='utf-8') as f:
exercises = [line.strip() for line in f if line.strip()]

with open(answer_file, 'r', encoding='utf-8') as f:
user_ans = [line.strip() for line in f if line.strip()]

correct = [] # 存答对的题号
wrong = [] # 存答错的题号

for i in range(len(exercises)):

ex = exercises[i].rstrip('=').strip()
ua = user_ans[i]

correct_val = eval_expr_str(ex)
user_val = parse_fraction(ua)

if correct_val == user_val:
correct.append(i + 1)
else:
wrong.append(i + 1)

with open('Grade.txt', 'w', encoding='utf-8') as f:
f.write(f"Correct: {len(correct)} ({', '.join(map(str, correct))})\n")
f.write(f"Wrong: {len(wrong)} ({', '.join(map(str, wrong))})\n")

五.测试运行

5.1
共设计 12 组测试用例,覆盖参数校验、功能约束、边界场景、性能、批改五大类,全部通过。

编号 测试类别 测试内容 输入 / 操作 预期结果 测试结果
1 参数校验 缺失 - r 参数 python main.py -n 10 报错并打印帮助信息 通过
2 参数校验 -r 为 0 python main.py -n 5 -r 0 提示 - r 必须为正整数 通过
3 功能验证 单题生成 python main.py -n 1 -r 10 生成 1 道合法题目,答案正确 通过
4 约束校验 减法非负 生成 100 道题逐题检查 所有减法子表达式左值≥右值,结果非负 通过
5 约束校验 除法结果为分数 生成 100 道题逐题检查 所有除法结果分母均不为 1 通过
6 去重验证 小范围生成 python main.py -n 50 -r 5 50 道题规范串全部唯一,无重复 通过
7 格式验证 分数输出格式 查看 Exercises.txt 真分数 a/b,带分数 i’f/d,格式符合要求 通过
8 格式验证 括号正确性 生成含 3 个运算符的题目 括号符合优先级与左结合规则,无多余括号 通过
9 批改功能 全对批改 用生成的 Answers.txt 批改 Correct: 总数,Wrong: 0 通过
10 批改功能 错题批改 修改 3 道题答案后批改 准确统计对错题号,数量与修改一致 通过
11 性能测试 万道题生成 python main.py -n 10000 -r 20 无重复题目 通过
12 边界测试 带分数运算 构造1’1/2 + 1/2 = 答案为2,计算正确 通过

5.2正确性说明:
测试覆盖了所有需求点:参数处理合法、生成题目全部满足数值范围、减法非负、除法结果为分数、运算符不超过 3 个、题目不重复等约束;批改功能可准确判分并统计题号;万级题目生成性能达标。因此可以确定程序功能正确,符合全部需求。

六.PSP 表格(实际耗时)

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

七.项目小结

7.1项目成果
本次结对项目完整实现了全部需求:

  • 支持-n控制题目数量、-r控制数值范围,参数缺失或非法时有明确报错
  • 生成的题目满足减法非负、除法结果为分数、运算符不超过 3 个等所有约束
  • 实现了基于交换律的题目去重,保证题目不重复
  • 支持自然数、真分数、带分数的运算与格式输出
  • 具备题目批改功能,可统计对错数量与题号并输出 Grade.txt
  • 性能支持万道题目稳定生成

7.2经验与教训
数据结构选对事半功倍。最初考虑过直接字符串拼接生成题目,但括号处理和去重都非常麻烦。改用表达式树后,求值、去重、格式化都可以通过递归优雅实现,代码量和复杂度大幅降低。
同时要注重边界测试,-r=1、-r=2等小数值范围场景,最容易暴露重试逻辑、除法约束等方面的 bug,是测试的重中之重,不能只测常规范围。

7.3结对感受

  • 吴圣林:两个人讨论需求和方案时,能快速发现彼此的思维盲区,比如去重规则的边界情况,一个人很容易考虑不周。代码复审阶段也能快速发现笔误和逻辑漏洞,比单人自查效率高很多。
  • 林锶堉:从对方身上学到了很多编码习惯,比如变量命名规范、边界条件处理的思路。
posted @ 2026-09-22 19:32  梣雨  阅读(8)  评论(0)    收藏  举报