软件工程第三次作业——结对作业
软件工程第三次作业——结对作业
个人项目作业 | 实现一个自动生成小学四则运算题目的命令行程序(也可以用图像界面,具有相似功能) |
---|---|
这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience |
这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience/homework/13470 |
这个作业的目标 | 训练协同项目软件开发能力,学会使用性能测试工具和实现单元测试优化 |
基本信息
本项目GitHub连接:https://github.com/sywaaaa/Pair-Project/tree/master
本项目参与人员
人员 | 学号 |
---|---|
谢斯越 | 3123004673 |
郑哲磊 | 3123004682 |
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 15 |
Estimate | 估计这个任务需要多少时间 | 10 | 10 |
Development | 开发 | 5 | 5 |
Analysis | 需求分析(包括学习新技术) | 40 | 50 |
Design Spec | 生成设计文档 | 25 | 20 |
Design Review | 设计复审 | 15 | 10 |
Coding Standard | 代码规范(为目前的开发制定合适的规范) | 10 | 10 |
Design | 具体设计 | 30 | 25 |
Coding | 具体编码 | 100 | 90 |
Code Review | 代码复审 | 20 | 15 |
Test | 测试(自我测试,修改代码,提交修改) | 30 | 40 |
Reporting | 报告 | 10 | 10 |
Test Report | 测试报告 | 10 | 10 |
Size Measurement | 计算工作量 | 5 | 5 |
Postmortem & Process Improvement Plan | 事后总结,并提出过程改进计划 | 10 | 5 |
一、效能分析
- 性能改进目标
在初版系统中,主要耗时集中在:
表达式计算:evaluate_expression 内使用正则和 eval 解析字符串,重复构造 Fraction。
生成题目判重:is_duplicate 使用简单字符串排序检测重复,随题量增大复杂度提高。
文件 IO:每题逐行写入文件,频繁调用导致性能下降。
- 改进思路
优化方向 改进方法 效果
表达式计算 使用单次正则替换数字为 Fraction,避免多次 split
文件写入 生成完所有题目和答案后一次性写入
判重逻辑 简化表达式比较方式,避免多次重复转换
-
性能分析
生成题目:
可见,大部分的时间都在判断生成的题目是否重复,较难为优化
打分:
打分时,通过表达式求得相应结果的时间占用较多 -
最耗时函数
| 函数 | 说明 |
| --------------------- | ---------------- |
|is_duplicate
| 检测题目是否重复 |
|evaluate_expression
| 返回表达式结果 |
二、设计与实现过程
- 模块组织
main.py → 程序入口,解析参数并分发任务
generator.py → 生成题目与标准答案(支持括号、分数、带分数)
evaluator.py → 表达式求值(Fraction运算,处理负数/非法表达式)
checker.py → 判分并生成 Grade.txt
utils.py → 工具函数(分数格式化、随机生成操作数、判重)
-
模块关系图
-
核心函数流程图(以generate_exercises为例)
三、关键代码说明
- 判分模块(checker.py)
from evaluator import evaluate_expression
from utils import format_fraction
def check_answers(exercise_file, answer_file):
"""比对题目与答案并输出 Grade.txt"""
with open(exercise_file) as f:
exercises = [line.strip(" =\n") for line in f if line.strip()]
with open(answer_file) as f:
answers = [line.strip() for line in f if line.strip()]
correct, wrong = [], []
for i, (expr, ans) in enumerate(zip(exercises, answers), start=1):
val = evaluate_expression(expr)
std = format_fraction(val) if val is not None else None
if std == ans:
correct.append(i)
else:
wrong.append(i)
with open("Grade.txt", "w") as f:
f.write(f"Correct: {len(correct)} ({', '.join(map(str, correct))})\n")
f.write(f"Wrong: {len(wrong)} ({', '.join(map(str, wrong))})\n")
说明:
evaluate_expression(expr) 负责解析题目字符串并返回 Fraction 类型的值;
format_fraction() 将 Fraction 转换为自然数、真分数或带分数格式;
判分逻辑:对比标准答案和用户答案,分别统计正确和错误题号;
优化点:批量统计后一次性写入文件,减少 IO 调用。
- 表达式求值(evaluator.py)
def convert_to_fraction(expr: str):
"""
将表达式中的数字转换为 Fraction 类型字符串。
支持:
- 自然数:3 -> Fraction(3,1)
- 真分数:3/5 -> Fraction(3,5)
- 带分数:2'3/4 -> Fraction(11,4)
"""
def repl(match):
token = match.group()
if "'" in token: # 带分数
whole, frac = token.split("'")
num, den = frac.split("/")
total_num = int(whole) * int(den) + int(num)
return f"Fraction({total_num},{den})"
elif "/" in token: # 真分数
num, den = token.split("/")
return f"Fraction({num},{den})"
else: # 自然数
return f"Fraction({token},1)"
expr = expr.replace("×", "*").replace("÷", "/")
expr = re.sub(r"\d+'?\d*/?\d*", repl, expr)
return expr
def evaluate_expression(expr: str):
"""计算表达式结果,返回 Fraction,非法或负数返回 None"""
expr = convert_to_fraction(expr)
try:
value = eval(expr, {"__builtins__": None}, {"Fraction": Fraction})
if value < 0:
return None
return value
except Exception:
return None
说明:
先将题目表达式中的数字全部转换为 Fraction,避免浮点误差;
支持四则运算和括号;
eval() 在受限的环境下安全执行;
负数或非法表达式返回 None,便于判题过滤。
- 题目生成模块(generator.py)
def generate_expression(r):
"""随机生成一个支持括号的四则运算表达式"""
op_count = random.randint(1, 3) # 操作符数量
tokens = [generate_operand(r)] # 第一个操作数
# 随机添加运算符和操作数
for _ in range(op_count):
op = random.choice(["+", "-", "×", "÷"])
tokens.append(op)
tokens.append(generate_operand(r))
expr = " ".join(tokens)
# 随机添加括号,保证语法合法
if op_count >= 1 and random.random() < 0.5:
expr = add_random_parentheses(tokens)
return expr
def generate_exercises(n, r):
"""生成 n 道题目,并保存 Exercises.txt 与 Answers.txt"""
expressions, answers = [], []
while len(expressions) < n:
expr = generate_expression(r)
# 过滤除零、负值和重复题目
if any(op in expr for op in ["÷ 0", "/ 0"]):
continue
value = evaluate_expression(expr)
if value is None or value < 0:
continue
if is_duplicate(expr, expressions):
continue
expressions.append(expr)
answers.append(format_fraction(value))
# 批量写入文件
with open("Exercises.txt", "w", encoding="utf-8") as f:
for e in expressions:
f.write(e + " =\n")
with open("Answers.txt", "w", encoding="utf-8") as f:
for a in answers:
f.write(a + "\n")
说明:
随机生成操作数和操作符,最多 3 个运算符;
50% 概率随机加入括号,保证合法;
判题前先计算,保证不生成非法或负数题目;
判重逻辑利用 is_duplicate() 简单判断加法/乘法交换律。
4.工具模块(utils.py)
def generate_operand(r: int):
"""随机生成自然数或真分数"""
choice = random.random()
if choice < 0.3: # 30%生成真分数
numerator = random.randint(1, r-1)
denominator = random.randint(2, r)
return f"{numerator}/{denominator}"
else:
return str(random.randint(0, r-1))
python
复制代码
def format_fraction(frac: Fraction):
"""将 Fraction 转换为输出格式"""
if frac.denominator == 1:
return str(frac.numerator)
elif frac.numerator > frac.denominator:
whole = frac.numerator // frac.denominator
remainder = frac.numerator % frac.denominator
if remainder == 0:
return str(whole)
return f"{whole}'{remainder}/{frac.denominator}"
else:
return f"{frac.numerator}/{frac.denominator}"
def is_duplicate(expr, existing_exprs):
"""判断表达式是否重复(考虑加/乘交换律)"""
simple = expr.replace(" ", "")
for e in existing_exprs:
if sorted(simple.replace("×", "*").replace("÷", "/")) == \
sorted(e.replace(" ", "").replace("×", "*").replace("÷", "/")):
return True
return False
说明:
generate_operand() 生成随机操作数(自然数或真分数);
format_fraction() 保证 Fraction 输出为自然数、真分数或带分数格式;
is_duplicate() 简单判断重复题目,防止生成重复题。
四、测试样例
在测试中,我们生成的题目如下
5/4 × 3 =
4/10 + 3 + ( 5 ÷ 1/7 ) =
5 × 3/4 × 8 =
2/9 × 1 ÷ ( 4/3 × 8/2 ) =
0 × 8 =
6 - 0 × 1 =
1 × 4/7 ÷ 3 + 7 =
9 - ( 2 + 8/6 ) =
4 ÷ 2 =
0 × 7/4 × 6/6 =
其对应的相关答案分别为
3'3/4
38'2/5
30
1/24
0
6
7'4/21
5'2/3
2
0
在这之后测试答题
3'3/4
38
30
1/12
0
6
7
5'2/3
3
0
得到以下输出结果grade.txt
Wrong: 4 (2, 4, 7, 9)
验证了我们程序的正确
五、项目小结
- 成果与亮点
支持分数、带分数及括号运算;
生成题目支持随机运算符数量与括号嵌套;
判分功能可统计正确与错误题号,输出 Grade.txt;
性能优化后提升了执行效率。
- 遇到问题
带分数与真分数正则匹配复杂;
判重逻辑需考虑加/乘交换律。
- 经验与教训
模块化设计显著降低维护难度;
Fraction 精确计算避免浮点误差;
性能分析工具对优化方向非常有帮助。
- 结对感受
谢斯越:郑哲磊在判分逻辑优化上提出许多宝贵建议,极大提升了程序效率。
郑哲磊:谢斯越在模块设计与整体架构上思路清晰,保证了代码整洁易扩展。
我们两人通过频繁讨论与代码复审,实现了高效合作与知识共享。