作业3——结对项目

自动生成四则运算程序

属性 详细信息
作业要求 https://edu.cnblogs.com/campus/gdgy/SoftwareEngineeringClassof2023/homework/13326
所属班级 https://edu.cnblogs.com/campus/gdgy/SoftwareEngineeringClassof2023
开发者 饶博勋(3123004152)杨超民(3123004161)
Github仓库 https://github.com/RaoHush/Cooperation-Project

PSP表格

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

设计实现过程

本程序用于通过给定参数随机生成四则运算题目和答案。以下是使用说明,各功能函数设计及分析。

使用说明

生成python main.py -n<生成题目数量> -r<数值范围>
检验python main.py -e<题目文件> -a<答案文件>

题目结果保存到Exercises.txt
答案结果保存到Answers.txt
正确率结果保存到Grade.txt

各函数设计与分析

真分数处理函数

def format_fraction(f):
    if isinstance(f, fractions.Fraction):
        if f.denominator == 1:
            return str(f.numerator)
        whole = f.numerator // f.denominator
        remainder = f.numerator % f.denominator
        if whole > 0:
            return f"{whole}'{remainder}/{f.denominator}" if remainder else f"{whole}"
        return f"{f.numerator}/{f.denominator}"
    return str(f)

用于将假分数转换为真分数,统一格式

算式生成函数

def generate_number(r, non_zero=False):#生成安全数值

    if random.choice([True, False]):
        return random.randint(1 if non_zero else 0, r - 1)
    else:
        numerator = random.randint(1, r - 1)
        denominator = random.randint(max(numerator + 1, 2), r)
        if non_zero:
            while denominator == 0:
                denominator = random.randint(max(numerator + 1, 2), r)
        return fractions.Fraction(numerator, denominator)


def generate_expression(r, ops_remaining=1):
    if ops_remaining == 0:
        num = generate_number(r)
        return format_fraction(num), num

    left_ops = random.randint(0, ops_remaining - 1)
    right_ops = ops_remaining - 1 - left_ops
    operator = random.choice(['+', '-', '×', '÷'])

    # 递归生成子树
    e1_str, e1_val = (generate_expression(r, left_ops) if left_ops > 0 else
                      (format_fraction(generate_number(r)), generate_number(r)))
    e2_str, e2_val = (generate_expression(r, right_ops) if right_ops > 0 else
                      (format_fraction(generate_number(r)), generate_number(r)))

    # 处理运算符约束
    if operator == '-':
        if e1_val < e2_val:
            e1_str, e2_str = e2_str, e1_str
            e1_val, e2_val = e2_val, e1_val
    elif operator == '÷':
        e2_str, e2_val = (format_fraction(generate_number(r, True)), generate_number(r, True))
        while e2_val == 0:
            e2_str, e2_val = (format_fraction(generate_number(r, True)), generate_number(r, True))

    # 交换律规范化:确保加法/乘法的操作数按字典序排列
    if operator in ['+', '×']:
        if e1_str > e2_str:  # 字典序比较
            e1_str, e2_str = e2_str, e1_str
            e1_val, e2_val = e2_val, e1_val

    # 智能括号优化
    op_priority = {'+': 1, '-': 1, '×': 2, '÷': 2}
    e1_needs_parens = any(op in e1_str for op in ['+', '-']) and op_priority[operator] > 1
    e2_needs_parens = any(op in e2_str for op in ['+', '-']) and op_priority[operator] > 1

    if e1_needs_parens:
        e1_str = f"({e1_str})"
    if e2_needs_parens:
        e2_str = f"({e2_str})"

    expr_str = f"{e1_str} {operator} {e2_str}"
    return expr_str, calculate_expression(expr_str)


def generate_problems(n, r):
    exercises = []
    answers = []
    seen_expressions = set()  # 用于记录已生成的表达式

    for problem_num in range(1, n + 1):
        for _ in range(100):
            ops_count = random.randint(1, 3)
            expr_str, answer = generate_expression(r, ops_count)

            # 检查表达式是否重复
            if expr_str in seen_expressions:
                continue
            seen_expressions.add(expr_str)

            if answer.denominator != 0 and answer >= 0:
                actual_ops = sum(expr_str.count(op) for op in ['+', '-', '×', '÷'])
                if actual_ops == ops_count:
                    verified_answer = format_fraction(calculate_expression(expr_str))
                    if format_fraction(answer) == verified_answer:
                        exercises.append(f"{problem_num}. {expr_str} = ")
                        answers.append(f"{problem_num}. {verified_answer}")
                        break
        else:
            raise ValueError(f"生成失败,请增大-r值(当前r={r})")

    with open("Exercises.txt", "w") as f:
        f.write("\n".join(exercises))
    with open("Answers.txt", "w") as f:
        f.write("\n".join(answers))

generate_number用于生成符合规范的数字
generate_expression用于生成算式。
-1.使用递归方式生成算式,随机生成子树以随机运算符的个数;
-2.对有更多限制的除法和减法有单独的处理;
-3.随机生成括号;
-4.规范化算式,避免重复生成一样的算式。
generate_problems用于生成问题,判断重复表达式,将算式与答案写入txt文件
generate_problems使用generate_expression,generate_expression使用generate_number

结果计算函数

def calculate_expression(expr):
    try:
        expr = expr.replace('×', '*').replace('÷', '/')
        expr = re.sub(r"(\d+)'(\d+)/(\d+)", r"Fraction(\1*\3+\2,\3)", expr)
        expr = re.sub(r"(\d+)/(\d+)", r"Fraction(\1,\2)", expr)
        expr = re.sub(r"\b(\d+)\b", r"Fraction(\1,1)", expr)

        for match in re.finditer(r"Fraction\(\d+,\s*(-?\d+)\)", expr):
            denominator = int(match.group(1))
            if denominator == 0:
                raise ZeroDivisionError("Generated expression has division by zero.")
        return eval(expr, {'__builtins__': None}, {'Fraction': fractions.Fraction})
    except ZeroDivisionError:
        raise
    except Exception as e:
        raise ValueError(f"Error calculating expression: {e}")

先处理计算符,再去除题号,检验是否有除以0的操作。

生成成绩函数

def grade_answers(exercise_file, answer_file):
    with open(exercise_file) as f:
        exercises = f.readlines()
    with open(answer_file) as f:
        answers = f.readlines()

    correct, wrong = [], []

    for ex, ans in zip(exercises, answers):
        try:
            # 解析题号
            ex_num, ex_expr = ex.strip().split('.', 1)
            ans_num, ans_val = ans.strip().split('.', 1)

            if int(ex_num) != int(ans_num):
                raise ValueError("题号不匹配")

            # 计算正确答案
            expr = ex_expr.split('=', 1)[0].strip()
            correct_answer = format_fraction(calculate_expression(expr))

            # 比对答案
            if ans_val.strip() == correct_answer:
                correct.append(int(ex_num))
            else:
                wrong.append(int(ex_num))
        except Exception as e:
            wrong.append(int(ex_num))  # 如果出错,也归类为错误答案

    # 输出结果
    with open("Grade.txt", "w") as f:
        f.write(f"Correct: {len(correct)} ({', '.join(map(str, sorted(correct)))})\n")
        f.write(f"Wrong: {len(wrong)} ({', '.join(map(str, sorted(wrong)))})\n")

解析题号,计算结果,对比答案,返回正确题目数量,错误题号。

性能分析

覆盖率

生成功能测试
image
校对功能测试
image

运行分析

image

测试运行

1.大量生成数据:可支持10000条算式生成;
2.重复性检测:当n数量过大,r范围过小时,有可能无法产生足够数量的算式(排列组合所有情况不够的),这时会提示调整r的取值;同时测试过查重。
image

3.除零检测:已在函数中规避。

项目小结

成果总结:

功能实现:成功开发四则运算题目自动生成与答案校验程序,可按用户指定数量和范围生成题目、计算答案,分别保存至文件,还能校验题目与答案文件并输出正确率。

经验总结

代码优化:使用集合记录已生成表达式避免重复,通过性能分析进一步优化代码,提高程序效率和稳定性。
错误处理:完善的错误处理机制是保证程序健壮性的关键,对可能出现的错误情况进行充分考虑并通过异常处理有效应对。
团队协作:沟通协作在设计和代码复审中发挥重要作用,能及时发现并修正潜在问题,提升项目质量。

posted @ 2025-03-19 14:58  Hush_Ha  阅读(37)  评论(0)    收藏  举报