结对项目

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Class78-Grade2024-CS
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class78-Grade2024-CS/homework/15703
这个作业的目标 完成结对项目: 实现一个自动生成小学四则运算题目的命令行程序
代码仓库 https://github.com/RettoST/PairHomework
姓名 宋浩霖
学号 3124004224
姓名 黄乐熙
学号 3124004208

1.PSP表格

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

2.性能分析

对 python main.py -n 10000 -r 100 进行性能分析,使用 cProfile:
图片
可见主要是生成表达式建树部分比较耗时

3.设计实现过程

3.1 模块划分

main.py         入口,参数解析,分发到生成模式/批改模式
model.py        表达式树节点 Node
formatter.py    分数格式化(整数、真分数、带分数)
evaluator.py    表达式求值 + 合法性检查
generator.py    题目生成 + 去重 + 输出文件
grader.py       批改模块,统计对错

3.2 关系图

flowchart LR main[main.py] --> gen[generator.py] main --> grade[grader.py] gen --> model[model.Node] gen --> ev[evaluator.py] gen --> fmt[formatter.py] ev --> model grade --> ev

3.3 关键数据结构

表达式树 Node:

class Node:
    value = None   # Fraction,叶子节点才有
    op = None      # '+', '-', '*', '/'
    left = None
    right = None

题目用三元组表示:(tree, expr_str, answer)

3.4 关键函数流程图

题目生成流程图

flowchart TD A[开始] --> B[随机生成运算符个数 1-3] B --> C[递归构建表达式树] C --> D[evaluate 求值] D --> E{合法?} E -- 否 --> B E -- 是 --> F[canonical 生成去重键] F --> G{重复?} G -- 是 --> B G -- 否 --> H[加入题目列表] H --> I{数量够了?} I -- 否 --> B I -- 是 --> J[写入 Exercises.txt 和 Answers.txt] J --> K[结束]

批改流程图

flowchart TD A[读取 Exercises.txt 和 Answers.txt] --> B[逐题循环] B --> C[解析题目] C --> D[求值] D --> E[与答案比较] E --> F{还有题?} F -- 是 --> B F -- 否 --> G[统计正确/错误编号] G --> H[写入 Grade.txt] H --> I[结束]

4代码说明

4.11 分数格式化(formatter.py)

关键点:假分数要转成带分数 2'3/8

def fraction_to_str(f: Fraction) -> str:
    if f.denominator == 1:
        return str(f.numerator)
    if abs(f.numerator) < f.denominator:
        return f"{f.numerator}/{f.denominator}"
    integer = f.numerator // f.denominator
    remain = f.numerator % f.denominator
    return f"{integer}'{remain}/{f.denominator}"

4.2 表达式求值(evaluator.py)

递归求值,同时做合法性检查

def evaluate(node):
    if node.is_leaf():
        return node.value
    left = evaluate(node.left)
    right = evaluate(node.right)
    if node.op == "+":
        return left + right
    if node.op == "-":
        if left < right:
            raise InvalidExpression("减法结果为负数")
        return left - right
    if node.op == "*":
        return left * right
    if node.op == "/":
        if right == 0:
            raise InvalidExpression("除数为 0")
        result = left / right
        if result.denominator == 1:
            raise InvalidExpression("除法结果为整数")
        if result >= 1:
            raise InvalidExpression("除法结果不是真分数")
        return result

4.3 去重(generator.py)

需求明确要求:+ 和 × 满足交换律,去重时要排序左右子树

def canonical(node):
    if node.is_leaf():
        return fraction_to_str(node.value)
    left = canonical(node.left)
    right = canonical(node.right)
    if node.op in ("+", "*") and left > right:
        left, right = right, left
    return f"({node.op} {left} {right})"

4.4 批改(grader.py)

把题目字符串转成 Fraction 表达式,关键是整数也要包成 Fraction,否则 Python 的 / 会走浮点

def eval_expr_string(s):
    s = s.replace("×", "*").replace("÷", "/").replace(" ", "")
    s = re.sub(r"(\d+)'(\d+)/(\d+)",
               r"(Fraction(\1,1)+Fraction(\2,\3))", s)
    s = re.sub(r"(\d+)/(\d+)", r"Fraction(\1,\2)", s)
    s = re.sub(r"(?<![\d)])(\d+)(?![\d(])", r"Fraction(\1)", s)
    return eval(s, {"Fraction": Fraction})

5.测试运行

测试用例(10 个)

# 命令 检查点
1 python main.py -n 10 -r 10 生成 10 题,答案都 < 10
2 python main.py -n 5 -r 1 只能生成 0,程序不崩溃
3 python main.py -n 5 -r 2 数值只含 0 和 1
4 python main.py -n 100 -r 100 100 题无重复
5 python main.py -n 10000 -r 100 10000 题能在 5 秒内生成
6 人工检查 Exercises.txt 运算符 ≤ 3 个
7 人工检查 Exercises.txt 减法不出负数
8 人工检查 Answers.txt 除法结果都是真分数
9 python main.py -e Exercises.txt -a Answers.txt 全部正确时 Wrong: 0
10 手动改错 3 题答案后批改 Wrong 列出对应编号

Exercises.txt
图片
Grade.txt
图片

图片
图片

6.项目小结

6.1 优势:

代码编写时候模块划分清晰:生成、求值、格式化、批改分块编写,后期改bug时定位了位置并修改的地方很小
Fraction全程统一:只要涉及数值运算的地方都用Fraction,避免了浮点误差

6.2 改错与已知问题:

批改器整数除法:eval("37/(92*94)") 返回 float,与答案Fraction(37, 8648)不相等。最开始10000 题测试暴露了576个错误,定位到是数字没包成 Fraction。找错误花了40分钟才彻底修好
去重没处理结合律:当前只处理交换律,1+2+3 和 3+(2+1) 会漏判

6.3 结对感受

shl:
这次结对最大的收获是不要一个人硬扛。批改器那个bug我卡了很久,hlx说"会不会是整数除法"结果还真是。以前觉得结对会拖慢进度,但现在发现卡壳时队友往往能看出自己纰漏的地方

hlx:
我主要负责设计文档和测试,shl写代码更快。分工明确之后效率很高。我们从一开始就约定每个commit只做一件事,一人写一次commit,确实效率很高

posted @ 2026-09-22 18:01  Retto  阅读(4)  评论(0)    收藏  举报