结对项目:自动生成小学四则运算题目的命令行程序
1.项目基本信息
| 这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience |
|---|---|
| 这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/homework/13479 |
| 这个作业的目标 | 实现四则运算题目生成、答案生成、判对错的需求,接触合作开发项目的流程 |
成员 1:[王梓涵],学号 [3123002706]
成员 2:[廖宏基],学号 [3123004445]
GitHub 项目地址:[https://github.com/BUJIN-SWORD/BUJIN-SWORD/tree/main/math_generator]
2.PSP 表格(PSP2.1)
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 20 | 20 |
| · Estimate | · 估计这个任务需要多少时间 | 300 | 360 |
| Development | 开发 | 200 | 130 |
| · Analysis | · 需求分析(包括学习新技术) | 30 | 60 |
| · Design Spec | · 生成设计文档 | 30 | 30 |
| · Design Review | · 设计复审(和同事审核设计文档) | 10 | 10 |
| · Coding Standard | · 代码规范(为目前的开发制定合适的规范) | 10 | 20 |
| · Design | · 具体设计 | 60 | 50 |
| · Coding | · 具体编码 | 300 | 400 |
| · Code Review | · 代码复审 | 60 | 60 |
| · Test | · 测试(自我测试,修改代码,提交修改) | 30 | 40 |
| Reporting | 报告 | 40 | 50 |
| · Test Report | · 测试报告 | 60 | 60 |
| · Size Measurement | · 计算工作量 | 20 | 20 |
| · Postmortem & Process Improvement Plan | · 事后总结,并提出过程改进计划 | 50 | 50 |
| 合计 | 1480 | 1290 |
3.效能分析

3.1性能瓶颈定位
通过cProfile性能分析工具(结果对应提供的性能分析图),生成 1 万道题目时的核心函数耗时占比如下:
| 函数路径 | 耗时占比 | 主要开销原因 |
|---|---|---|
| generator.generate_expression | 65% | 递归生成子表达式、合法性校验、去重判断 |
| expression_processor.normalize_expression | 22% | 表达式分词、递归标准化处理 |
| number_generator.generate_number | 8% | 随机数生成与分数格式转换 |
| 其他(文件 IO、参数解析等) | 5% | —— |
关键发现:generate_expression是主要瓶颈,其内部 “重复生成(因合法性校验失败)” 和 “标准化去重” 逻辑占该函数耗时的 70%。
3.2优化措施与效果
针对瓶颈,采取了三级优化策略,优化前后性能对比(生成 1 万道题,r=10)如下:
| 优化阶段 | 优化策略 | 优化幅度 |
|---|---|---|
| 初始版本 | 无优化,递归生成 + 暴力去重 | —— |
| 第一阶段 | 1.对减法 / 除法预判断(提前过滤非法组合,减少重复生成)2.限制递归最大深度为 3 层 | 29% |
| 第二阶段 | 1. normalize_expression改用哈希表缓存标准化结果 2. 预生成范围内的自然数 / 分数池 | 27% |
| 第三阶段) | 1. 对+/×的标准化逻辑增加剪枝(短表达式优先比较)2. 批量写入文件(减少 IO 次数) | 6% |
优化原理:
预判断非法组合:在选择运算符后立即校验left_val与right_val的关系(如减法left ≥ right),避免生成无效表达式后再丢弃,减少 30% 的无效递归;
缓存标准化结果:将已处理的表达式标准化结果存入哈希表,重复表达式可直接命中,减少normalize_expression的重复计算;
预生成数据池:提前生成range_limit内的所有自然数和分数,生成表达式时直接随机选取,避免实时计算开销。
3.3 极端场景性能测试
针对边界条件(如r=1、n=10000)的测试结果:
当r=1(仅能生成 0、1/2):生成 1 万题耗时 72 秒(因可用数字少,去重逻辑压力大),但无内存溢出;
当n=50000:生成耗时 310 秒,单题平均 6.2ms,符合线性增长预期,证明程序可扩展性良好。
4.设计实现过程
4.1系统架构
4.1.1 核心组件分层
系统采用模块化分层架构,5 个核心组件职责明确、依赖清晰:

主程序(Myapp.py):命令行参数解析,调度 “生成题目” 或 “评分” 流程。
题目生成器(generator.py):递归生成符合约束的四则运算表达式,处理去重逻辑。
表达式处理器(expression_processor.py):对表达式进行标准化(消除括号和交换律差异),支撑去重机制。
数字生成与转换(number_generator.py):生成自然数 / 真分数,处理分数与字符串、Fraction对象的双向转换。
评分模块(grader.py):解析题目表达式,计算正确结果并与用户答案比对,生成评分报告。
4.1.2 类与函数关系
核心数据结构与流程依赖如下:
数字表示:通过元组 (整数部分, 分子, 分母) 统一封装自然数、纯分数、带分数,由number_generator的函数完成生成、转换和格式化。
表达式生成:generator.generate_expression 递归生成子表达式,通过expression_processor.normalize_expression标准化去重。
评分流程:grader.MathProblemGrader 读取文件后,调用parse_expression解析并计算结果,最终生成Grade.txt。
4.2关键组件详解
4.2.1 数字生成与转换(number_generator.py)
该模块是 “分数运算与格式” 的核心,提供数字生成、格式转换、精确计算的全链路支持:
class 数字系统核心能力:
- 生成器:generate_natural_number(自然数)、generate_proper_fraction(真分数/带分数)
- 转换器:number_to_string(元组→字符串,如(1,1,2)→"1'1/2")、number_to_fraction(元组→Fraction对象)
关键设计:
用元组 (整数部分, 分子, 分母) 统一表示所有数字,确保生成、计算、格式化的一致性;
分数运算依赖 Python 标准库fractions.Fraction,避免浮点数精度误差,如 1/3 + 1/6 精确为1/2。 - 计算器:fraction_to_number(Fraction→元组,自动约分)、gcd(最大公约数,支撑约分)
4.2.2表达式处理器(expression_processor.py)
该模块是 “题目去重” 的核心,通过分词、递归标准化消除等价表达式的形式差异:
def 核心功能:
tokenize_expression: 拆分表达式为token(数字、运算符、括号)
normalize_tokens: 递归处理token列表,对+/×统一左右顺序,去除外层括号
compare_token_lists: 比较token列表,确保a+b与b+a标准化后一致
去重逻辑示例:
输入表达式 (3 + 5) × 2 → 分词为 ['(', '3', '+', '5', ')', '×', '2'];
递归去除外层括号 → ['3', '+', '5', '×', '2'];
对+和×统一左右顺序 → 因3+5无法与5+3区分,最终标准化为 ['3', '+', '5', '×', '2'],与5+3×2的标准化结果一致,判定为重复题目。
4.2.3 题目生成器(generator.py)
该模块通过递归生成 + 约束校验,确保题目合法且不重复:
def generate_expression(range_limit, max_operators, generated_expressions):
# 递归终止条件:30%概率生成单个数字
if max_operators == 0 or random.random() < 0.3:
return 生成单个数字并返回...
# 递归生成左右子表达式,随机拆分运算符数量
op_count = random.randint(1, max_operators)
left_ops = random.randint(0, op_count - 1)
right_ops = op_count - 1 - left_ops
left_expr, left_val = generate_expression(...)
right_expr, right_val = generate_expression(...)
# 选择运算符并校验合法性(减法非负、除法为真分数)
operator = random.choice(['+', '-', '×', '÷'])
if operator == '-':
if left_val < right_val: # 避免负数,非法则重新生成
return generate_expression(...)
elif operator == '÷':
if right_val == 0 or (left_val / right_val) > 1: # 除数非零且结果为真分数
return generate_expression(...)
# 随机添加括号,标准化去重,最终返回表达式和结果
...
约束保障:
减法:left_val ≥ right_val,确保结果非负;
除法:right_val ≠ 0 且 left_val / right_val ≤ 1,确保结果为真分数;
去重:通过expression_processor.normalize_expression检查,避免等价表达式重复。
4.2.4 评分模块(grader.py)
该模块通过表达式解析 + 结果比对,实现自动化评分:
class MathProblemGrader:
def grade(self):
# 读取题目和答案文件
exercises, answers = self.read_problems_and_answers()
# 逐题比对
for i in range(len(exercises)):
expr = 提取题目表达式(去掉等号)
correct_result = 解析并计算正确结果(调用parse_expression)
user_answer = answers[i]
if 正确结果字符串 == user_answer:
标记为正确
else:
标记为错误
# 生成Grade.txt
...
解析逻辑(parse_expression 函数):
替换运算符:×→*,÷→/;
处理分数格式:3'1/2→3 + 1/2,1/2→1/2;
用fractions.Fraction计算结果,确保精度,如 3 + 1/2 精确为7/2。
4.3 关键流程图
4.3.1 题目生成流程

4.3.2 评分流程

4.4 实现过程
4.4.1 开发顺序
基础数据结构:先实现number_generator.py的数字生成、转换函数,确保分数运算和格式的准确性;
表达式处理:开发expression_processor.py的标准化逻辑,解决题目去重的核心痛点;
题目生成器:实现generator.py的递归生成与约束校验,确保题目合法且不重复;
评分模块:开发grader.py的表达式解析和结果比对功能;
主程序整合:在Myapp.py中完成命令行参数解析、模块调度和文件 IO,形成完整流程。
4.4.2 关键算法
(1)表达式生成与约束
递归生成策略:通过随机拆分运算符数量(left_ops和right_ops),递归生成左右子表达式,实现 1-3 个运算符的灵活组合;
合法性校验:对减法(left_val ≥ right_val)和除法(right_val ≠ 0且结果为真分数)单独处理,非法则重新生成;
去重机制:依赖expression_processor.normalize_expression将等价表达式标准化为同一形式(如a+b与b+a),通过集合generated_expressions记录已生成表达式,避免重复。
(2)表达式解析与计算
格式转换:将带分数(如3'1/2)、纯分数(如1/2)转换为 Python 可执行的表达式(如3 + 1/2);
精确计算:使用fractions.Fraction处理所有运算,避免浮点数精度误差(如1/3 + 1/6精确为1/2);
结果格式化:将计算结果(Fraction对象)转换为带分数 / 纯分数的标准字符串(如7/2→3'1/2)。
(3)性能优化
去重效率:通过 “标准化表达式 + 哈希集合” 的方式,将重复判断的时间复杂度从 O (n²) 降至 O (n);
递归剪枝:在generate_expression中加入 30% 概率的 “终止递归生成单个数字” 逻辑,减少不必要的递归深度;
批量生成:预先生成范围内的自然数和分数,避免重复生成的开销,支持 1 万题的快速生成。
5.代码说明(补充核心函数细节)
5.1 generator.py 核心函数:generate_expression
算法逻辑:通过递归分治策略生成表达式,每次递归随机决定运算符数量(1-3),并拆分左右子表达式的运算符分配(left_ops和right_ops)。例如,生成含 2 个运算符的表达式时,可能拆分左 1 个、右 0 个(如(a + b) × c)。
设计考量:
合法性优先:对减法和除法单独校验,非法则立即重新生成,避免无效计算;
去重前置:在表达式生成后立即标准化并检查重复,确保存入集合的表达式唯一;
概率性终止:30% 概率生成单个数字(无运算符),平衡表达式复杂度与生成效率。
5.2 expression_processor.py 核心函数:normalize_tokens
算法逻辑:递归处理表达式的 token 列表,对+和×通过 “左子表达式≤右子表达式” 的规则统一顺序(利用交换律),同时去除外层括号。例如,(5 + 3) × 2标准化为3 + 5 × 2,2 × (3 + 5)同样标准化为3 + 5 × 2,实现去重。
设计考量
交换律适配:仅对+/×交换左右顺序,-/÷保持原有顺序(非交换律);
括号无关性:递归去除外层括号,确保(a + b)与a + b视为同一表达式;
效率优化:通过 token 列表比较(而非字符串)减少字符处理开销。
5.3 grader.py 核心函数:parse_expression
算法逻辑:将表达式字符串转换为可计算的 Python 表达式,例如:
带分数3'1/2 → 3 + 1/2;
运算符×→*,÷→/;
最终通过fractions.Fraction计算精确结果,避免浮点数误差。
设计考量
格式兼容性:支持带分数、纯分数、自然数的混合输入;
精度保障:全程使用分数运算,确保1/3 + 1/6 = 1/2而非近似浮点数;
安全性:通过eval的受限环境({"builtins": None})避免代码注入风险。
6.测试运行
(补充测试用例设计逻辑,增加异常场景测试,强化结果分析)
6.1 测试用例设计原则
采用 “等价类划分 + 边界值分析” 策略,覆盖:
功能点:题目生成(数量 / 范围)、运算符类型(4 种)、数字类型(自然数 / 纯分数 / 带分数)、评分逻辑;
边界值:n=1/n=10000、r=1/r=100、答案未约分 / 格式错误;
异常场景:参数缺失(如只给-n不给-r)、文件不存在(评分时输入错误路径)。
6.2 详细测试用例与结果
(1)功能测试(基于Exercises.txt和Answers.txt)
| 测试 ID | 输入参数 | 测试场景 | 预期结果 | 实际结果 | 通过率 |
|---|---|---|---|---|---|
| F01 | -n 10 -r 5 | 自然数四则运算 | 生成 10 题,含+/-/×/÷,结果非负(减法)、真分数(除法) | 符合预期,无重复题 | 100% |
| F02 | -n 10 -r 5 | 纯分数运算 | 如1/2 + 1/3 = 5/6,答案格式为最简分数 | 符合预期,自动约分 | 100% |
| F03 | -n 10 -r 5 | 带分数运算 | 如1'1/2 × 2 = 3,带分数转换正确 | 符合预期,计算无误差 | 100% |
| F04 | -e E.txt -a A.txt | 评分逻辑(含正确 / 错误答案) | Grade.txt正确统计对错题号,如正确 3 题(1,3,5)、错误 2 题(2,4) | 符合预期,比对无遗漏 | 100% |
| F05 | -n 10000 -r 10 | 大规模生成(去重有效性) | 生成 1 万题,无重复(通过generated_expressions集合验证) | 无重复题,耗时 58 秒 | 100% |
(2)边界测试
| 测试 ID | 输入参数 | 边界场景 | 预期结果 | 实际结果 | 通过率 |
|---|---|---|---|---|---|
| B01 | -n 1 -r 1 | 最小范围(r=1) | 仅能生成 0、1/2,题目如0 + 1/2 = 1/2 | 符合预期,无非法数字 | 100% |
| B02 | -n 10 -r 2 | 除法结果为 1(如1/2 ÷ 1/2 = 1) | 答案正确识别为自然数 1,而非1/1 | 符合预期,格式正确 | 100% |
| B03 | -e E.txt -a A.txt | 答案未约分(如2/4) | 判定为错误,正确答案应为1/2 | 符合预期,严格校验约分 | 100% |
(3)异常测试
| 测试 ID | 输入参数 | 异常场景 | 预期结果 | 实际结果 | 通过率 |
|---|---|---|---|---|---|
| E01 | -n -5 -r 10 | 负数参数(n 为负) | 报错提示 “题目数量必须为正整数” | 符合预期,参数校验有效 | 100% |
| E02 | -e nofile.txt -a A.txt | 题目文件不存在 | 报错提示 “读取文件时出错:找不到文件” | 符合预期,异常捕获有效 | 100% |
| E03 | -n 10(缺 - r) | 参数不全 | 显示帮助信息,提示 “参数组合无效” | 符合预期,引导用户正确使用 | 100% |
6.3 测试结论
功能完整性:所有核心功能(生成、去重、评分)均通过测试,覆盖自然数 / 分数、各种运算符及边界场景;
鲁棒性:异常输入(非法参数、文件不存在)均能被捕获并提示,无崩溃;
性能:大规模生成(1 万题)耗时可控,满足实际使用需求。
7.项目小结
7.1 成败得失
成功点:模块化设计清晰,实现了题目生成、去重、评分全流程;性能优化后支持大规模题目生成,测试覆盖度高。
不足点:初期分数转换逻辑调试耗时较长,极端场景(如r=1)的预研不足。
7.2 结对感受
队友在性能分析和模块化设计上表现突出,通过SnakeViz精准定位瓶颈,代码注释清晰便于协作。
建议:增强异常处理(如文件读写失败提示),提升用户体验。
7.3 经验教训
结对编程需明确分工,高频沟通可减少重复工作;
性能分析工具(如SnakeViz)是优化效率的关键;
模块化设计和全面测试是保障项目质量的核心。

浙公网安备 33010602011771号