软件工程结对项目小学四则运算自动生成程序

项目成员

姓名:王昊圆,学号:3224004232
姓名:苏拉娅・斯地克阿吉,学号:3224004384
Github 项目地址:https://github.com/wang1580/four-arithmetic

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

效能分析

本次针对四则运算题目生成程序进行性能调优,改进程序性能总共耗时约 40 分钟,分为性能采样、定位瓶颈、代码优化、验证效果四个步骤。

性能分析结果

使用 Python 内置cProfile工具采集程序运行数据,并用 SnakeViz 可视化性能报告,性能截图如下:

本次测试生成 10000 道四则运算题目,程序总运行耗时为 0.527 s,和截图顶部builtins.exec总耗时一致。
从 Icicle 冰柱图可以直观看到函数调用栈与耗时占比:消耗时间最多的自定义函数是normalize_expr(表达式标准化函数),也就是图中橙色区块对应的函数,为本程序的性能瓶颈。normalize_expr内部嵌套的递归norm函数,反复执行 token 分割、列表拼接与操作数比较,占用了程序绝大部分自定义代码 CPU 耗时。
image

性能改进思路

  1. 优化前的问题
    项目早期版本做题目去重时,没有做表达式标准化。每生成一道新题目,需要和之前所有题目字符串逐个对比,时间复杂度为(O(n^2))。当题目数量变大时,比对次数急剧增加,程序运行缓慢。同时加法、乘法存在交换律,1+2和2+1属于等价题目,旧方法无法识别这类重复题目。
  2. 优化方案
    新增normalize_expr表达式标准化函数,递归解析表达式,利用加法、乘法交换律,交换操作数顺序,让等价表达式转换成完全相同的字符串。使用集合set保存已经生成过的标准化表达式。集合查找的时间复杂度为(O(1)),不再需要和全部历史题目逐一比对,大幅降低去重开销。
  3. 优化效果验证
    优化完成后,使用cProfile采集性能数据。生成 10000 道四则运算题目耗时 0.527 秒,程序运行速度得到明显提升。性能瓶颈转移到normalize_expr函数,原因是表达式标准化需要递归解析表达式并处理字符串,存在一定开销。如果后续还需要继续提升性能,可以优化递归逻辑、减少字符串拼接操作。

设计实现过程

代码组织

本项目使用 Python 语言开发,采用函数式编程,没有自定义类,整体划分为 5 个核心函数模块,各函数之间相互调用配合完成全部功能:

  1. 分数处理模块:基于 Python 内置Fraction类,实现自然数、真分数、带分数的格式化输出,支持2’3/8格式转换;保证减法不会产生负数,除法运算结果为真分数。

  2. 表达式生成模块:随机生成算术表达式,控制每道题目运算符数量不超过 3 个;支持随机插入括号;生成表达式时做约束校验,减法保证被减数大于等于减数,除法结果为真分数。

  3. 表达式标准化与去重模块:递归遍历表达式抽象语法树,对加法、乘法子表达式进行排序,生成唯一签名;利用集合实现题目去重,满足作业要求:交换 +、× 左右操作数视为同一道题目。

  4. 命令行参数解析模块:解析-n、-r、-e、-a四种参数。-r参数必须传入,缺少-r时程序输出帮助信息并终止运行。程序分为两种运行模式:题目生成模式、判题模式。

  5. 判题统计模块:读取Exercises.txt题目文件与Answers.txt答案文件,逐行解析题目并计算标准答案,和用户答案比对;收集正确、错误题号,按照指定格式输出到Grade.txt。

  6. 解析命令行参数,判断运行模式:

    • 生成模式:-n指定题目数量,-r指定数值范围;
    • 判题模式:-e指定题目文件路径,-a指定答案文件路径。
  7. 生成模式:循环生成表达式,校验表达式合法性并做去重;题目写入Exercises.txt,对应答案写入Answers.txt。

  8. 判题模式:读取题目文件与答案文件,逐题计算标准答案并比对;统计正确、错误题号,按照固定格式写入Grade.txt。

代码说明

1. 分数格式化函数:实现真分数、带分数输出

from fractions import Fraction

def frac_to_str(f: Fraction) -> str:
    """将Fraction对象转换为作业要求字符串格式:3/5 或者 2'3/8"""
    if f.denominator == 1:
        return str(f.numerator)
    int_part = f.numerator // f.denominator
    frac_num = f.numerator % f.denominator
    if int_part == 0:
        return f"{frac_num}/{f.denominator}"
    else:
        return f"{int_part}'{frac_num}/{f.denominator}"
2. 表达式标准化签名函数:实现题目去重
def normalize_expr(expr):
    """递归生成表达式唯一签名,加法、乘法交换操作数得到相同签名"""
    expr = expr.replace("( ","").replace(" )","")
    tokens = expr.split()
    def norm(tok_list):
        if len(tok_list)==1:
            return [tok_list[0]]
        mid = 1
        left = tok_list[:mid]
        op = tok_list[mid]
        right = tok_list[mid+1:]
        l_norm = norm(left)
        r_norm = norm(right)
        if op in ('+','×'):
            if l_norm > r_norm:
                l_norm, r_norm = r_norm, l_norm
        return l_norm + [op] + r_norm
    return " ".join(norm(tokens))

3.判题模块核心函数:比对答案并输出 Grade.txt
def grade(ex_file, ans_file):
    with open(ex_file,"r",encoding="utf-8") as f:
        ex_lines = [line.strip() for line in f if line.strip()]
    with open(ans_file,"r",encoding="utf-8") as f:
        ans_lines = [line.strip() for line in f if line.strip()]
    correct = []
    wrong = []
    for idx, (ex, std_ans) in enumerate(zip(ex_lines, ans_lines), start=1):
        std_frac = str_to_frac(std_ans)
        try:
            user_frac = str_to_frac(ans_lines[idx-1])
            if user_frac == std_frac:
                correct.append(idx)
            else:
                wrong.append(idx)
        except:
            wrong.append(idx)
    with open("Grade.txt","w",encoding="utf-8") as f:
        f.write(f"Correct: {len(correct)} ({', '.join(map(str,correct))})\n")
        f.write(f"Wrong: {len(wrong)} ({', '.join(map(str,wrong))})\n")

## 测试运行

本次共设计 10 组测试用例,覆盖全部需求点,验证程序正确性:
1. **用例 1:基础题目生成** `python four.py -n 10 -r 10`
预期结果:生成 10 道四则运算题目写入 Exercises.txt,答案写入 Answers.txt;每道题目运算符≤3;减法子表达式不会出现负数;除法结果为真分数。
2. **用例 2:缺少 - r 参数测试** `python four.py -n 10`
预期结果:程序检测到缺少`-r`参数,输出帮助信息并报错退出。
3. **用例 3:大批量题目生成** `python four.py -n 10000 -r 100`
预期结果:稳定生成 10000 道不重复题目,程序不崩溃,题目全部满足约束条件。
4. **用例 4:加法交换去重测试**
预期结果:不会同时生成`2 + 3 =` 和 `3 + 2 =`,二者视为同一题目,自动去重。
5. **用例 5:乘法交换去重测试**
预期结果:不会同时生成`2 × 3 =` 和 `3 × 2 =`,二者视为同一题目,自动去重。
6. **用例 6:判题全对测试**
先生成 10 道题目,再执行判题命令`python four.py -e Exercises.txt -a Answers.txt`。预期 Grade.txt:`Correct: 10 (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)`,Wrong 为空。
7. **用例 7:手动制造错题判题测试**
手动修改 Answers.txt 中第 3 题对应的答案,执行判题。预期 Grade.txt 中 Wrong 列表包含题号 3。
8. **用例 8:真分数运算测试**
生成题目`1/6 + 1/8 =`,标准答案输出`7/24`,分数计算准确无误。
9. **用例 9:带分数格式测试**
生成带分数表达式,输出格式为`2’1/2`,答案同样支持带分数格式解析与输出。
10. **用例 10:括号与多运算符测试**
生成最多 3 个运算符、带括号的复合表达式,表达式计算结果正确,括号逻辑合法。
## 项目小结

本次结对项目完成小学四则运算命令行程序,支持自然数、真分数、带分数、括号表达式,支持题目去重功能以及自动判题统计。
结对开发分工安排:王昊圆负责表达式随机生成模块、分数运算模块的代码编写与单元测试;苏拉娅・斯地克阿吉负责命令行参数解析、表达式签名去重、判题模块开发,同时完成文档撰写与博客整理。开发过程中两人互相进行代码复审,共同排查边界 bug,例如减法负数、除法结果合法性、交换律题目重复等问题。

### 项目收获

1. 掌握命令行参数解析、文件读写、分数精确运算,规避浮点数运算带来的精度丢失问题。
2. 理解表达式抽象语法树、表达式归一化签名思路,解决加法、乘法交换带来的题目重复判定难题。
3. 体会结对编程优势:双人互相审查代码,单人容易忽略的边界逻辑问题,通过讨论可以快速发现并修复。

### 项目不足与改进方向

当前表达式随机生成逻辑偶尔会生成多余冗余括号,后续可以优化括号生成策略;一万道题目虽然经过性能优化,但随机生成逻辑仍有优化空间,可以进一步提升生成速度。

### 结对互评

- 王昊圆:苏拉娅・斯地克阿吉在代码复审阶段非常细心,能够快速发现边界条件漏洞;建议后续项目在需求分析阶段就一起讨论,更早划分模块任务。
- 苏拉娅・斯地克阿吉:王昊圆负责的分数运算模块稳定可靠,调试能力强;建议在调试过程中增加更多中间日志打印,方便快速定位异常。
### 结对感受
本次结对开发让我们真切体会结对编程的价值。在需求分析阶段两人一起研读题目要求,避免单方面理解需求出现偏差;代码编写阶段分工开发,代码复审阶段互相检查代码缺陷。很多单人写代码容易忽略的边界条件,通过双人讨论能够提前发现并修复。同时我们也学会了合理分配任务、沟通协调,是一次很好的实践锻炼。
posted @ 2026-09-19 20:52  王昊圆  阅读(20)  评论(0)    收藏  举报