结对项目:自动生成小学四则运算题目的命令行程序

结对项目博文:自动生成小学四则运算题目命令行程序

姓名/学号:阮洪建 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.pytracemalloc):一次运行耗时约 10.17s,峰值追踪内存约 1.98MB(仅统计 Python 追踪到的内存分配,系统总内存占用更高但远低于 50MB)。
    • 不同测量方法与系统负载会产生差异,但多次运行结果均满足 ≤ 10 秒目标。
  • 生成 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

表达式生成流程(简化)

  1. 随机选择生成简单表达式或复杂表达式。
  2. 对于 -÷
    • 生成满足 a>=b0<a/b<1 的操作数,否则退化为其他运算。
    • 强制插入括号确保子表达式约束不被其他运算破坏。
  3. 多步构造:逐步追加运算符与操作数,更新当前值以快速检查非负。
  4. 校验表达式整体非负,失败则重试或回退到简单加法兜底。

判题流程

  1. 解析题目行:index, expression = read_exercise_line(line),去除末尾 =
  2. 计算表达式值,格式化为字符串(最简分数或带分数)。
  3. 从答案文件读取对应编号的答案,解析为 Fraction 比较。
  4. 统计正确与错误,输出到 Grade.txt(编号按升序)。

去重哈希计算

  • 解析表达式为 AST:使用改造的 Shunting-yard 算法处理运算符优先级与括号。
  • 在每个 + / × 节点,仅交换左右使其字典序一致;保留括号以表达结构;禁止跨层扁平化(防止错误地将不同结合结构视为相同)。
  • 将标准化字符串做 md5 哈希,写入 set 做 O(1) 去重判断。

设计取舍与边界情况

  • 仅使用标准库,避免外部依赖;分数使用 fractions.Fraction 自动约分。
  • 判题允许题目与答案文件存在空行与不合法行,解析时跳过以增强鲁棒性。
  • 生成表达式设置最大尝试次数上限,保证在高重复率时仍能较快完成任务。

四、代码说明(核心片段)

  1. 分数合法性校验:
@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
  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})"
  1. 判题逻辑:
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 + 33 + (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
posted @ 2025-10-20 18:45  AAA天天开心  阅读(23)  评论(0)    收藏  举报