结对项目
| 这个作业属于哪个课程 | 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,确实效率很高
浙公网安备 33010602011771号