结对项目
| 课程 | 软件工程 |
|---|---|
| 这个作业要求在哪里 | 作业要求 |
| 这个作业的目标 | 实现小学四则运算题目自动生成、自动判题功能 |
张高朗3124004262
Part 1 github链接
https://github.com/3214153741-sudo/four_calc_project
Part 2 PSP表格(时间预估)
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) |
|---|---|---|
| Planning | 计划 | 30 |
| ·Estimate | 估计这个任务需要多少时间 | 30 |
| Development | 开发 | 270 |
| ·Analysis | 需求分析(包括学习新技术) | 60 |
| ·Design Spec | 生成设计文档 | 30 |
| ·Design Review | 设计复审 | 20 |
| ·Coding Standard | 代码规范 | 10 |
| ·Design | 具体设计 | 40 |
| ·Coding | 具体编码 | 60 |
| ·Code Review | 代码复审 | 20 |
| ·Test | 测试(自我测试,修改代码,提交修改) | 30 |
| Reporting | 报告 | 60 |
| ·Test Report | 测试报告 | 20 |
| ·Size Measurement | 计算工作量 | 10 |
| ·Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 30 |
| 合计 | 360 |
Part 3 效能分析
- 优化前:递归生成表达式,容易大量产生非法题目(减法负数、除零、假分数),需要大量丢弃重试,效率低。
- 改进思路:提前对子表达式求值校验,非法节点直接返回 None,提前剪枝,减少无效递归;增加表达式字符串集合去重,避免重复题目。
- 性能分析图:用
pyinstrument性能剖析工具,执行批量生成 1000 道四则运算题目,采集程序 CPU 耗时数据,生成交互式性能报告,找到消耗最大的函数:gen_expr()是最耗时的函数
![]()
Part 4 代码实现过程
2 个类
ValueNode:数字叶子节点,代表单个数字(整数 / 分数)
方法:evaluate()求值;to_string()转为字符串表达式BinOpNode:二元运算节点,保存左子树、运算符、右子树
方法:evaluate()求值并校验合法性;to_string()拼接带括号表达式
4 个核心函数
gen_expr(max_depth, num_range)
递归生成 AST 表达式树,最耗时核心函数。递归创建左右子节点,构造二元运算节点,求值校验,非法直接返回 None 完成剪枝。generate_problem_set(count, num_range)
题目集合生成器,循环调用gen_expr,使用集合去重,收集足够多合法不重复题目,返回题目列表、答案列表。write_files(problems, answers)
文件输出函数,把题目写入Exercises.txt,标准答案写入Answers.txt。check_answer()
判题函数:读取标准答案文件、用户作答文件,逐题比对,统计对错,输出Grade.txt。

Part 5 代码说明
本次节选核心代码为抽象语法树节点定义、递归表达式生成、合法性校验、题目去重模块。文件读写、判题等辅助功能代码较为简单,不在此处展示,完整源码放在博客末尾附录。
ValueNode、BinOpNode构成抽象语法树。evaluate()在求值同时校验规则:减法不能得到负数、除法不能除零、除法结果必须为真分数或整数;非法表达式直接返回None,实现递归剪枝。使用Fraction分数类型,彻底避免浮点数精度丢失问题。gen_expr():递归生成表达式树,控制嵌套括号深度。生成子表达式后立刻校验,非法直接丢弃,减少无效递归,是性能优化的核心。generate_problem_set():循环调用递归生成函数,使用集合存储表达式字符串,依靠集合元素唯一性完成题目去重,收集指定数量合法题目。
from fractions import Fraction
import random
# 数字叶子节点:AST树的叶子,保存数值
class ValueNode:
def __init__(self, val):
self.val = Fraction(val)
def evaluate(self):
return self.val
def to_string(self):
return str(self.val)
# 二元运算节点:保存左右子树与运算符,负责求值+合法性校验
class BinOpNode:
def __init__(self, left, op, right):
self.left = left
self.op = op
self.right = right
def evaluate(self):
l_val = self.left.evaluate()
r_val = self.right.evaluate()
if self.op == '+':
return l_val + r_val
elif self.op == '-':
res = l_val - r_val
return res if res >= 0 else None
elif self.op == '*':
return l_val * r_val
elif self.op == '÷':
if r_val == 0:
return None
res = l_val / r_val
return res if res.numerator <= res.denominator else None
def to_string(self):
return f"({self.left.to_string()}{self.op}{self.right.to_string()})"
# 核心递归函数:生成表达式AST,递归剪枝
def gen_expr(max_depth, num_range=10):
if max_depth <= 1:
return ValueNode(random.randint(1, num_range))
ops = ['+', '-', '*', '÷']
op = random.choice(ops)
left = gen_expr(max_depth - 1, num_range)
right = gen_expr(max_depth - 1, num_range)
node = BinOpNode(left, op, right)
return node if node.evaluate() is not None else None
# 批量生成题目,集合去重
def generate_problem_set(count, num_range=10):
expr_set = set()
problems, answers = [], []
while len(problems) < count:
ast = gen_expr(3, num_range)
if ast is None: continue
expr_str = ast.to_string()
if expr_str not in expr_set:
expr_set.add(expr_str)
problems.append(expr_str)
answers.append(str(ast.evaluate()))
return problems, answers
Part 6 测试用例
此处我在打包好了的含exe文件的文件夹终端输命令生成10个测试数

此处是生成的十个测试用例,和对应答案及检测之后的结果

说明
打开生成的Exercises.txt,文件内包含 10 条合法四则表达式:表达式减法无负数结果、除法不会出现除零、除法结果仅保留整数或真分数,所有题目不存在重复。
Answers.txt中存储每道题对应的计算结果。
Grade.txt记录每道题的批改判定结果,本次测试使用标准答案进行自检,全部题目判定正确,程序功能符合预期。
Part 7 完善表格
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 30 | 25 |
| · Estimate | · 估计这个任务需要多少时间 | 30 | 25 |
| Development | 开发 | 270 | 310 |
| · Analysis | · 需求分析(包括学习新技术) | 60 | 65 |
| · Design Spec | · 生成设计文档 | 30 | 32 |
| · Design Review | · 设计复审 | 20 | 23 |
| · Coding Standard | · 代码规范 | 10 | 10 |
| · Design | · 具体设计 | 40 | 45 |
| · Coding | · 具体编码 | 60 | 72 |
| · Code Review | · 代码复审 | 20 | 25 |
| · Test | · 测试(自我测试,修改代码,提交修改) | 30 | 38 |
| Reporting | 报告 | 60 | 55 |
| · Test Report | · 测试报告 | 20 | 18 |
| · Size Measurement | · 计算工作量 | 10 | 12 |
| · Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 25 |
| 合计 | 360 | 390 |
Part 8 项目小结
本次独立完成小学四则运算生成器项目,使用 Python 实现核心功能,在 VSCode 环境下开发,最终打包为可独立运行的 exe 程序,达成了全部预期需求:能够随机生成合法四则运算题目,支持分数运算,保证减法结果非负、除法无除零错误,可输出题目文件并自动判题。
- 巩固了 Python 模块化编程、命令行参数解析、文件读写的基础能力;
- 深入练习了分数处理、表达式合法性校验、随机表达式去重等算法逻辑;
- 掌握了 PyInstaller 打包 Python 脚本为 exe 可执行文件的方法,学会处理打包过程中出现的依赖问题;
我觉得后续可以增加图形化界面,不只是命令行交互,降低使用门槛,增加更多题型配置选项,例如可选择是否启用括号、是否只使用整数运算。或者增加错题统计功能,读取用户作答文件,统计错误题目并输出错题集。
(本次项目是结对项目,但是项目是我自己独立完成,并没有体验到结对的感觉,希望下次有机会尝试)


浙公网安备 33010602011771号