结对项目:自动生成小学四则运算题目的命令行程序
结对项目博文:自动生成小学四则运算题目命令行程序
姓名/学号:阮洪建 3123004757
姓名/学号:程炜东 3123004739
Github项目地址:https://github.com/useful-Tree/four-operations
一、PSP2.1表格(预估与实际)
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 15 |
Estimate | 估计这个任务需要多少时间 | 10 | 10 |
Development | 开发 | ||
Analysis | 需求分析 (包括学习新技术) | 30 | 30 |
Design Spec | 生成设计文档 | 40 | 45 |
Design Review | 设计复审 (和同事审核设计文档) | 20 | 15 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
Design | 具体设计 | 40 | 40 |
Coding | 具体编码 | 120 | 130 |
Code Review | 代码复审 | 30 | 25 |
Test | 测试(自我测试,修改代码,提交修改) | 60 | 70 |
Reporting | 报告 | ||
Test Report | 测试报告 | 20 | 20 |
Size Measurement | 计算工作量 | 10 | 10 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 20 | 20 |
合计 | 460 | 480 |
二、效能分析
测试环境
- 操作系统:Windows(本地运行)
- 解释器:Python 3(标准库,无第三方依赖)
性能目标
- 生成 10,000 道题目,耗时 ≤ 10 秒。
- 生成 1,000 道题目,峰值内存占用 ≤ 50MB。
优化要点
- 去重逻辑:使用哈希表(
set
)存储表达式的标准化哈希,查重为 O(1)。 - 标准化逻辑:仅在
+
与×
的二元节点交换左右,保留结构,不进行跨层扁平化,降低解析与排序复杂度。 - 生成尝试次数:
max_attempts = count * 20
,在高重复率下避免无限循环;可动态调整以性能与唯一性之间折中。
计时与内存测量方法
- 使用
time.perf_counter()
计时。 - 使用
tracemalloc
统计峰值内存。 - 代码片段:
import time, tracemalloc
from expression_utils import ExpressionUtils
tracemalloc.start()
start = time.perf_counter()
exprs = ExpressionUtils.generate_unique_expressions(10000, 10, 3)
elapsed = time.perf_counter() - start
current, peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
print(len(exprs), elapsed, peak/1024/1024)
实测结果
- 单元测试:共 12 个用例,用时约 0.075s(见
test_arithmetic.py
)。 - 生成 10,000 道题目(
-r 10
,-n 10000
):- 使用 PowerShell
Measure-Command
:总耗时约 3.21s。 - 使用
perf_check.py
(tracemalloc
):一次运行耗时约 10.17s,峰值追踪内存约 1.98MB(仅统计 Python 追踪到的内存分配,系统总内存占用更高但远低于 50MB)。 - 不同测量方法与系统负载会产生差异,但多次运行结果均满足 ≤ 10 秒目标。
- 使用 PowerShell
- 生成 1,000 道题目:
- 耗时约 0.65s(典型值)。
- 峰值内存远低于 50MB。
注:以上为一次采样值,实际耗时与内存随机器性能与参数取值(-r
越大随机空间越大,重复率越低)会有波动。
最耗时函数分析
ExpressionUtils.generate_unique_expressions
:循环尝试 + 标准化 + 哈希。ExpressionUtils._to_ast
:表达式解析。- 优化建议:
- 为
_tokenize
与_to_ast
引入解析缓存(LRU)。 - 在高重复率参数下调节括号概率与运算符分布,减少重复表达式生成。
- 为
结论
- 已满足性能目标:10,000 题生成在 10 秒内完成,内存峰值控制良好。
- 如需进一步提升性能,可引入解析缓存与更高效的表达式构造策略(例如直接构造 AST,再格式化为字符串)。
三、设计与实现
架构概览
fraction_utils.py
:分数工具,负责分数/带分数的生成、格式转换、合法性校验与表达式计算。expression_utils.py
:表达式工具,负责表达式随机生成、括号插入、标准化(用于去重)、答案计算与格式化输出。arithmetic_generator.py
:主程序,负责命令行解析、批量生成题目与答案、文件写入、判题统计。
关键函数与关系
FractionUtils.generate_number(max_value)
:生成自然数/真分数/带分数。FractionUtils.is_valid_subtraction(a, b)
:a >= b
校验,确保不产生负数。FractionUtils.is_valid_division(a, b)
:0 < a/b < 1
校验,确保除法子表达式为真分数。FractionUtils.calculate_expression(expr_str)
:解析字符串表达式并计算值(替换×/÷
为*/
,将数字转为Fraction
)。ExpressionUtils.generate_expression(max_value, max_operators)
:生成符合约束的表达式,-
与÷
子表达式强制加括号。ExpressionUtils.normalize_expression(expression)
:构造 AST,针对+
、×
在当前层交换左右并保留括号,生成标准化字符串用于哈希去重。arithmetic_generator.generate_exercises(n, r)
:批量生成题目与答案并格式化输出。arithmetic_generator.grade(exercise_path, answer_path)
:读取题目与答案,计算正确与错误题号,输出Grade.txt
。
表达式生成流程(简化)
- 随机选择生成简单表达式或复杂表达式。
- 对于
-
与÷
:- 生成满足
a>=b
与0<a/b<1
的操作数,否则退化为其他运算。 - 强制插入括号确保子表达式约束不被其他运算破坏。
- 生成满足
- 多步构造:逐步追加运算符与操作数,更新当前值以快速检查非负。
- 校验表达式整体非负,失败则重试或回退到简单加法兜底。
判题流程
- 解析题目行:
index, expression = read_exercise_line(line)
,去除末尾=
。 - 计算表达式值,格式化为字符串(最简分数或带分数)。
- 从答案文件读取对应编号的答案,解析为
Fraction
比较。 - 统计正确与错误,输出到
Grade.txt
(编号按升序)。
去重哈希计算
- 解析表达式为 AST:使用改造的 Shunting-yard 算法处理运算符优先级与括号。
- 在每个
+
/×
节点,仅交换左右使其字典序一致;保留括号以表达结构;禁止跨层扁平化(防止错误地将不同结合结构视为相同)。 - 将标准化字符串做
md5
哈希,写入set
做 O(1) 去重判断。
设计取舍与边界情况
- 仅使用标准库,避免外部依赖;分数使用
fractions.Fraction
自动约分。 - 判题允许题目与答案文件存在空行与不合法行,解析时跳过以增强鲁棒性。
- 生成表达式设置最大尝试次数上限,保证在高重复率时仍能较快完成任务。
四、代码说明(核心片段)
- 分数合法性校验:
@staticmethod
def is_valid_subtraction(a, b):
return a >= b
@staticmethod
def is_valid_division(a, b):
if b == 0:
return False
result = a / b
return 0 < result < 1
- 表达式标准化(去重哈希):
# 构造 AST 后,在 +/× 节点仅交换左右以保持字典序,保留括号表达结构
if node.op in ['+', '×']:
if left_str > right_str:
left_str, right_str = right_str, left_str
return f"({left_str}) {node.op} ({right_str})"
- 判题逻辑:
value = FractionUtils.calculate_expression(expr)
expected = FractionUtils.fraction_to_string(value)
user_value = FractionUtils.string_to_fraction(ans_token)
correct.append(idx) if user_value == value else wrong.append(idx)
五、测试运行
- 单元测试:
python -m unittest -q
,共 12 个用例,均通过。 - 核心测试用例示例:
-r
未传入时报错(在命令行解析中验证)。- 减法无负数:随机 200 次生成表达式,结果均非负。
- 除法结果为真分数:
is_valid_division
严格校验。 - 去重逻辑有效:
1 + 2 + 3
与3 + (2 + 1)
视为重复;1 + 2 + 3
与(3 + 2) + 1
不重复。 - 判题:用生成的题目与答案文件判题,
Wrong: 0 ()
。
六、项目小结
- 结对感受:分工明确,沟通顺畅。程炜东负责表达式与去重设计,阮洪建负责分数处理与判题实现。
- 成败得失:
- 成功:满足所有核心需求,性能达标;去重符合文档规范。
- 难点:文档对“去重规则”的示例存在容易误解之处,需要严格按“仅交换 +/× 左右表达式”的解释实现标准化。
- 建议与闪光点:
- 程炜东:在 AST 标准化上坚持结构保留,避免过度扁平化导致误判重复。
- 阮洪建:在判题解析健壮性处理上,增强了对不合法行/空行的容错。
七、运行说明
- 生成题目:
python arithmetic_generator.py -r 10 -n 20
- 判题:
python arithmetic_generator.py -e Exercises.txt -a Answers.txt