结对项目

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

一、项目信息

这个作业属于哪个课程 计科23级34班
这个作业要求在哪里 结对项目
这个作业的目标 熟悉结对项目开发流程,积累团队合作开发的实践经验

二、PSP表格

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

三、效能分析

3.1 性能优化时间

一共花费 40 分钟。

3.2 改进思路

  • snakeviz profile_results.prof 分析性能

图片1

  • 分析结果:

图片2

图片3

(1)文件I/O操作(最显著)
问题:文件打开操作耗时较多
优化: 批量文件写入、处理检查

图片4

图片5

(2)模块导入开销
优化:在函数内部导入,避免启动时加载

图片6

(3)预编译正则表达式
优化: 避免重复编译,提升性能

  • 修改前:
def safe_eval_expression(expr: str) -> Fraction:
    # 每次调用都重新编译正则表达式
    expr = re.sub(r"(-?\d+)'(\d+)/(\d+)", r"Fraction(\1 * \3 + \2, \3)", expr)
    expr = re.sub(r'\b(\d+)/(\d+)\b', r"Fraction(\1, \2)", expr)
expr = re.sub(r'\b(\d+)\b', r"Fraction(\1)", expr)
  • 修改后:
# 模块级别预编译,只编译一次
MIXED_FRACTION_PATTERN = re.compile(r"(-?\d+)'(\d+)/(\d+)")
FRACTION_PATTERN = re.compile(r'\b(\d+)/(\d+)\b')
INTEGER_PATTERN = re.compile(r'\b(\d+)\b')
OPERATOR_SPLIT_PATTERN = re.compile(r'([*/])')
PLUS_MINUS_SPLIT_PATTERN = re.compile(r'([+-])')

def safe_eval_expression(expr: str) -> Fraction:
    # 使用预编译的正则表达式
    expr = MIXED_FRACTION_PATTERN.sub(r"Fraction(\1 * \3 + \2, \3)", expr)
    expr = FRACTION_PATTERN.sub(r"Fraction(\1, \2)", expr)
    expr = INTEGER_PATTERN.sub(r"Fraction(\1)", expr)
  • 改进结果:

图片7

  • _io.open_code(打开代码文件):
    优化前:11次,0.003986s(单次 0.0003623s)
    优化后:11次,0.000994s(单次 0.00009081s)
    ✅性能提升约 75%。
  • _imp.create_dynamic(创建动态模块):
    优化前:3次,0.003337s(单次 0.001112s)
    优化后:3次,0.001038s(单次 0.000346s)
    ✅性能提升约 69%。

3.3 消耗最大的函数

def build_expression(bound: int, max_depth: int, force_operator=False) -> CalcNode:

四、设计实现过程

4.1 工作流程

  • 生成模式:主程序 → 题目生成器 → 表达式构建器 → CalcNode → 文件输出
  • 检查模式:主程序 → 文件读取 → 表达式计算器 → 答案比较 → 输出

4.2 代码组织

图片18

  • 类与函数关系图

图片19

  • 关键函数流程图

图片20

  • build_expression 递归构建流程图

图片21

五、代码说明

5.1 核心类:CalcNode

图片17

设计思路:

  • 采用二叉树结构表示算术表达式
  • 支持两种节点类型:叶子节点(常量)和内部节点(运算符)
  • 实现递归计算和遍历

5.2 关键代码说明

(1)表达式生成核心逻辑

def build_expression(bound: int, max_depth: int, force_operator=False) -> CalcNode:
    """递归构建算术表达式"""
    if max_depth <= 0 or (not force_operator and random.random() < 0.3):
        return CalcNode(const=make_random_value(bound))
    operators = ['+', '-', '*', '/']     # 定义可用的四则运算符
    for _ in range(20):
        op = random.choice(operators)
        # 递归生成左右子表达式(深度减1)
        left_expr = build_expression(bound, max_depth - 1)
        right_expr = build_expression(bound, max_depth - 1)

        if op == '-':
            left_val = left_expr.compute()
            right_val = right_expr.compute()
            if left_val < right_val:
                left_expr, right_expr = right_expr, left_expr
        try:
            # 构造候选表达式节点
            candidate = CalcNode(lhs=left_expr, oper=op, rhs=right_expr)
            if candidate.op_count() > 3:   # 运算符总数不能超过3个
                continue
            if not candidate.validate_subtraction():   # 所有减法子表达式必须满足 e1 >= e2(防止中间结果为负)
                continue
            if not candidate.validate_division():    # 所有除法子表达式结果必须是真分数(0 <= result < 1)
                continue
            result = candidate.compute()
            if result < 0:     # 最终计算结果不能为负数
                continue
            return candidate
        except (ZeroDivisionError, ValueError):
            continue     # 如果出现除零或非法操作,跳过此次尝试
    return CalcNode(const=make_random_value(bound))

设计思路:

  • 递归 + 随机生成
  • 减法防负数
  • 失败回退

(2)安全表达式计算

def safe_eval_expression(expr: str) -> Fraction:
    """安全计算表达式,完全使用分数运算"""
    if not expr or expr.isspace():
        raise ValueError("空表达式")
    try:
        # 清理表达式
        expr = expr.strip()

        # 处理带分数格式 - 先转换带分数
        def replace_mixed_fraction(match):
            whole = match.group(1)
            numer = match.group(2)
            denom = match.group(3)
            return f"({whole} + {numer}/{denom})"

        expr = MIXED_FRACTION_PATTERN.sub(replace_mixed_fraction, expr)
        expr = expr.replace('×', '*').replace('÷', '/')

        # 处理普通分数
        expr = FRACTION_PATTERN.sub(r"Fraction(\1, \2)", expr)
        # 处理整数
        expr = INTEGER_PATTERN.sub(r"Fraction(\1)", expr)

        # 使用更安全的方式计算
        result = eval(expr, {"Fraction": Fraction, "__builtins__": {}})

        if isinstance(result, (int, float)):
            return Fraction(result).limit_denominator()
        return result
    # 异常处理
    except ZeroDivisionError:
        raise ValueError("除零错误")
    except (ValueError, SyntaxError) as e:
        # 提供更详细的错误信息
        raise ValueError(f"表达式语法错误: {e} ")
    except Exception as e:
        raise ValueError(f"计算错误: {e}")

设计思路:

  • 使用 Fraction 实现无限精度分数运算,避免浮点误差
  • 分阶段转换表达式
  • 对空输入、除零、语法错误等异常情况提供清晰的错误提示,保证程序稳定运行。

六、测试运行

(1)命令行输入 python main.py -n 100 -r 10:
图片8

图片9

(2)程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计:

图片10

图片11

(3)单元测试用例:
部分代码:

图片12

图片13

  • 测试结果:
    ✅ 分数格式化:整数、真分数、带分数、负分数的正确渲染
    ✅ 表达式计算:基本运算、混合运算、括号优先级、错误处理
    ✅ 题目生成:随机表达式构建、去重机制、运算规则验证
    ✅ 答案解析:各种格式的答案解析和错误处理
    ✅ 边界情况:极端值、大量题目生成、单次运算等

  • 集成测试从题目生成 → 文件写入 → 文件读取 → 答案检查 → 评分输出,功能完整,有完善的错误处理和边界情 况处理。能够高效生成大量不重复题目

七、项目小结

7.1 项目成就

  1. 功能完整覆盖需求:成功实现命令行参数控制,支持生成包含自然数、真分数(含带分数)、运算符(+、-、×、÷)及括号的四则运算题目,同时完成题目与答案的文件存储及答案校验统计功能。
  2. 严格满足生成约束:生成的题目均符合核心约束,逻辑严谨性并经多轮测试验证。
  3. 支持大规模场景:程序可稳定生成一万道题目,无内存溢出或性能卡顿,文件读写效率满足实用需求。
  4. 代码可维护性强:采用模块化设计,逻辑清晰,便于后续扩展或修改。

7.2 出现问题及解决方案

  • 问题:在开发小学四则运算题目生成器时,我们遇到了eval函数在处理分数运算时出现浮点数精度问题,导致计算结果不准确。
    一开始程序生成的题目在人工验证时完全正确但使用-e -a参数进行自动批改时,出现好几个"错误"题目。

具体错误示例:

题目 11: 3/26 + 23 / 25
程序计算结果: 1.0353846153846153 (浮点数)
正确答案: 673/650 (分数)

原因:

expr = "3/26 + 23 / 25"
result = eval(expr)  # 返回浮点数 1.0353846153846153
correct_answer = Fraction(673, 650)  # 精确分数
# 浮点数与分数比较,因精度问题导致不相等
  • 解决方案:
    显式控制数值类型,表达式计算结果直接以Fraction对象存储,与答案文件中的分数字符串解析后的Fraction对象直接比较。
    优化后,所有分数运算结果均保持精确,自动批改功能的准确率达到 100%。

7.3 结对感受

  • 吴佳童:在这个合作过程中,我们能很快定位好分工,共同商讨好时间分配,我发现对方能灵活分配时间,平衡学业课程上的冲突来完成项目,且在测试代码功能上严谨,能在项目上优化提出更多建议。
  • 李恺凝:这次结对项目,我和搭档的协作非常顺畅。从需求分析阶段开始,我们就习惯边讨论边画流程图,合作中遇到分数精度问题时,我们没有各自纠结,而是一起查资料、试方案,最终确定用自定义分数类解决。这次经历不仅让我学会了如何在团队中发挥自己的优势,也从搭档身上学到了对细节的关注和解决问题的耐心。
posted @ 2025-10-21 20:29  拉赫玛尼诺芙  阅读(4)  评论(0)    收藏  举报