软件工程第三次作业——结对作业

软件工程第三次作业——结对作业

个人项目作业 实现一个自动生成小学四则运算题目的命令行程序(也可以用图像界面,具有相似功能)
这个作业属于哪个课程 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

一、效能分析

  1. 性能改进目标

在初版系统中,主要耗时集中在:

表达式计算:evaluate_expression 内使用正则和 eval 解析字符串,重复构造 Fraction。

生成题目判重:is_duplicate 使用简单字符串排序检测重复,随题量增大复杂度提高。

文件 IO:每题逐行写入文件,频繁调用导致性能下降。

  1. 改进思路

优化方向 改进方法 效果
表达式计算 使用单次正则替换数字为 Fraction,避免多次 split
文件写入 生成完所有题目和答案后一次性写入
判重逻辑 简化表达式比较方式,避免多次重复转换

  1. 性能分析
    生成题目:
    生成题目
    可见,大部分的时间都在判断生成的题目是否重复,较难为优化
    打分:
    打分
    打分时,通过表达式求得相应结果的时间占用较多

  2. 最耗时函数
    | 函数 | 说明 |
    | --------------------- | ---------------- |
    | is_duplicate | 检测题目是否重复 |
    | evaluate_expression | 返回表达式结果 |

二、设计与实现过程

  1. 模块组织
main.py          → 程序入口,解析参数并分发任务
generator.py     → 生成题目与标准答案(支持括号、分数、带分数)
evaluator.py     → 表达式求值(Fraction运算,处理负数/非法表达式)
checker.py       → 判分并生成 Grade.txt
utils.py         → 工具函数(分数格式化、随机生成操作数、判重)
  1. 模块关系图
    模块关系

  2. 核心函数流程图(以generate_exercises为例)
    函数流程

三、关键代码说明

  1. 判分模块(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 调用。

  1. 表达式求值(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,便于判题过滤。

  1. 题目生成模块(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)

验证了我们程序的正确

五、项目小结

  1. 成果与亮点

支持分数、带分数及括号运算;

生成题目支持随机运算符数量与括号嵌套;

判分功能可统计正确与错误题号,输出 Grade.txt;

性能优化后提升了执行效率。

  1. 遇到问题

带分数与真分数正则匹配复杂;

判重逻辑需考虑加/乘交换律。

  1. 经验与教训

模块化设计显著降低维护难度;

Fraction 精确计算避免浮点误差;

性能分析工具对优化方向非常有帮助。

  1. 结对感受

谢斯越:郑哲磊在判分逻辑优化上提出许多宝贵建议,极大提升了程序效率。
郑哲磊:谢斯越在模块设计与整体架构上思路清晰,保证了代码整洁易扩展。
我们两人通过频繁讨论与代码复审,实现了高效合作与知识共享。

posted on 2025-10-19 01:03  April_Xie  阅读(46)  评论(0)    收藏  举报

导航