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

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience/homework/13470
这个作业的目标 练习合作完成一个项目

小组成员及项目信息

姓名 学号
徐新曜 3123004634
许国伟 3123004200

Github项目地址: math-generator-cli

PSP表格

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

效能分析

在本次项目开发过程中,我们投入了约30分钟用于性能分析和优化。主要优化思路包括:

  1. 内存使用优化:在生成大量题目时,使用集合(set)来存储已生成的题目,确保题目唯一性的同时提高查找效率。

  2. 算法优化

    • 改进了分数生成算法,避免重复计算
    • 优化了括号插入算法,减少不必要的字符串拼接操作
  3. 文件操作优化:使用更高效的文件写入方式,减少I/O操作次数。

经过优化后,程序在生成10000道题目的情况下,内存占用降低了约15%,生成速度提升了约20%。

  1. 效能图:

    image

设计实现过程

整体架构设计

本项目采用模块化设计,将功能划分为多个独立的模块,便于维护和扩展。整体架构如下:

main.py - 程序入口点
├── argument_parser.py - 命令行参数解析
├── generator.py - 题目生成器
├── calculator.py - 答案计算器
├── validator.py - 答案校验器
├── file_handler.py - 文件处理
├── utils.py - 工具函数
└── config.py - 配置和常量定义

类和函数设计

  1. Main类:程序入口,协调各模块工作
  2. ArgumentParser类:解析和验证命令行参数
  3. Generator类:负责生成四则运算题目,支持整数和分数运算
  4. Calculator类:计算表达式结果,处理分数运算
  5. Validator类:校验答案正确性
  6. FileHandler类:处理文件读写操作
  7. Utils类:提供各种工具函数

关键函数流程图

以下是题目生成的核心流程:

flowchart TD A[开始生成题目] --> B{是否包含分数?} B -->|是| C[生成分数表达式] B -->|否| D[生成整数表达式] C --> E[随机选择运算符数量] D --> E E --> F[生成操作数] F --> G[选择安全的运算符] G --> H{是否插入括号?} H -->|是| I[插入括号] H -->|否| J[不插入括号] I --> K[格式化表达式] J --> K K --> L{是否达到题目数量?} L -->|否| B L -->|是| M[写入题目文件] M --> N[生成答案文件] N --> O[结束]

代码说明

1. 题目生成器(Generator类)

class Generator:
    def __init__(self):
        """初始化生成器"""
        self.operators = ['+', '-', '*', '/']
        self.questions = []
        self.used_questions = set()

    def generate_questions(self, count, range_limit, include_fractions=True, with_answers=False):
        """生成指定数量的题目,支持真分数"""
        print(f"正在生成 {count} 道题目,数值范围:0-{range_limit},包含真分数: {include_fractions}...")

        for i in range(1, count + 1):
            if include_fractions and random.random() < 0.5:  # 50%概率生成分数题目
                question = self.generate_fraction_expression(range_limit)
            else:
                question = self.generate_simple_expression(range_limit)

            self.questions.append((i, question))

        # 输出题目到文件
        from file_handler import FileHandler
        output_file = "questions.txt"
        if FileHandler.write_questions(output_file, self.questions):
            print(f"题目已生成并保存到: {output_file}")

        # 生成答案文件
        self.generate_answer_template(with_answers)

这个函数是题目生成的核心,根据参数决定生成整数题目还是分数题目,并支持自动生成答案。

2. 分数表达式生成

def generate_fraction_expression(self, range_limit, include_parentheses=True):
    """生成包含真分数的表达式,支持括号"""
    # 随机选择运算符数量(1-3个)
    operator_count = random.randint(1, 3)
    expression_parts = []

    # 生成第一个操作数
    if random.random() < 0.7:  # 70%概率生成分数
        num, den = Utils.generate_true_fraction(range_limit, allow_mixed=True)
        fraction_str = Utils.format_fraction(num, den, mixed=True)
        expression_parts.append(fraction_str)
    else:
        integer = Utils.random_number(range_limit)
        expression_parts.append(str(integer))

    # 生成后续的操作数和运算符
    for i in range(operator_count):
        # 选择运算符
        operator = self._get_safe_operator(expression_parts[-1], range_limit)
        expression_parts.append(operator)

        # 根据运算符类型生成下一个操作数
        if operator == '/':
            next_operand = self._generate_safe_divisor(expression_parts[-2], range_limit)
        elif operator == '-':
            next_operand = self._generate_safe_subtrahend(expression_parts[-2], range_limit)
        else:
            # 加法和乘法可以正常生成
            if random.random() < 0.7:  # 70%概率生成分数
                num, den = Utils.generate_true_fraction(range_limit, allow_mixed=True)
                fraction_str = Utils.format_fraction(num, den, mixed=True)
                next_operand = fraction_str
            else:
                next_operand = str(Utils.random_number(range_limit))

        expression_parts.append(next_operand)

    # 插入括号
    if include_parentheses and operator_count >= 1:
        return Utils.insert_parentheses(expression_parts)
    else:
        return " ".join(expression_parts)

这个函数负责生成包含真分数的表达式,支持括号,并确保生成的表达式符合数学规则(例如避免负数结果和除零错误)。

3. 计算器实现

def calculate_expression(self, expression):
    """计算表达式的值,支持真分数和带分数"""
    try:
        from fractions import Fraction
        import re

        # 将整数也转为Fraction,避免浮点精度问题
        def replace_number(match):
            num_str = match.group()
            if '/' in num_str or "'" in num_str:
                # 处理分数和带分数
                fraction_obj = Utils.parse_mixed_fraction(num_str)
                return f"Fraction({fraction_obj.numerator}, {fraction_obj.denominator})"
            else:
                # 处理整数
                return f"Fraction(int({num_str}), 1)"

        # 匹配所有数字(整数、分数、带分数)
        expr = re.sub(r"\d+'\d+/\d+|\d+/\d+|\d+", replace_number, expression)

        # 计算表达式结果
        result = eval(expr, {'Fraction': Fraction})

        # 确保返回Fraction对象
        if isinstance(result, Fraction):
            return result
        else:
            return Fraction(result)
    except Exception as e:
        Utils.print_error(f"计算表达式失败: {expression} - {str(e)}", exit_code=5)

计算器使用Python的Fraction类来处理分数运算,避免浮点精度问题,并支持解析和计算包含真分数和带分数的表达式。

4. 答案校验功能

def validate_answers(self, exercise_file, answer_file):
    """校验答案的正确性"""
    print(f"正在校验答案...")
    print(f"题目文件: {exercise_file}")
    print(f"答案文件: {answer_file}")

    # 读取题目和答案文件
    questions = FileHandler.read_questions(exercise_file)
    answers = FileHandler.read_answers(answer_file)

    # 验证文件格式
    if not FileHandler.validate_file_format(exercise_file, 'questions'):
        Utils.print_error("题目文件格式错误", exit_code=EXIT_CODES['FORMAT_ERROR'])

    if not FileHandler.validate_file_format(answer_file, 'answers'):
        Utils.print_error("答案文件格式错误", exit_code=EXIT_CODES['FORMAT_ERROR'])

    # 检查题目数量是否匹配
    if len(questions) != len(answers):
        Utils.print_error(
            f"题目和答案数量不匹配,题目数量: {len(questions)}, 答案数量: {len(answers)}",
            exit_code=EXIT_CODES['FORMAT_ERROR']
        )

    # 校验答案
    correct_numbers = []
    wrong_numbers = []

    for (q_num, question), (a_num, answer) in zip(questions, answers):
        if q_num != a_num:
            Utils.print_error(
                f"题目序号不匹配: 题目文件中第 {q_num} 题,答案文件中第 {a_num} 题",
                exit_code=EXIT_CODES['FORMAT_ERROR']
            )

        # 计算标准答案
        try:
            correct_answer = self.calculator.calculate_expression(question)
            formatted_correct = self.calculator.format_result(correct_answer)

            # 比较答案
            if self.compare_answers(answer, formatted_correct):
                correct_numbers.append(q_num)
            else:
                wrong_numbers.append(q_num)
        except Exception as e:
            Utils.print_error(f"计算第 {q_num} 题时发生错误: {str(e)}", exit_code=EXIT_CODES['INTERNAL_ERROR'])

这个函数负责校验用户提供的答案是否正确,包括文件格式验证、题目数量匹配检查,以及逐题比较答案。

5. 括号插入算法

def insert_parentheses(expression_parts):
    """在表达式中插入括号,改变运算顺序"""
    # 如果表达式少于1个运算符(2个操作数),不适合插入有意义的括号
    if len(expression_parts) < 3:  # 最少需要:operand operator operand
        return " ".join(expression_parts)

    # 将操作数和运算符分别提取
    operands = []
    operators = []

    for i, part in enumerate(expression_parts):
        if i % 2 == 0:  # 偶数索引是操作数
            operands.append(part)
        else:  # 奇数索引是运算符
            operators.append(part)

    n_operands = len(operands)

    # 根据操作数数量选择合适的括号插入策略
    if n_operands == 2:  # a op b
        return " ".join(expression_parts)
    elif n_operands == 3:  # a op b op c
        # 40%概率添加括号
        if random.random() < 0.6:
            return " ".join(expression_parts)

        # 选择括号模式:(a op b) op c 或 a op (b op c)
        if random.random() < 0.5:
            return f"({operands[0]} {operators[0]} {operands[1]}) {operators[1]} {operands[2]}"
        else:
            return f"{operands[0]} {operators[0]} ({operands[1]} {operators[1]} {operands[2]})"
    # ... 更多情况的处理

这个函数根据表达式的复杂度和操作数数量,智能地在表达式中插入括号,增加题目的难度和多样性。

测试运行

测试用例1: 基本的题目生成

python main.py -n 10 -r 10

具体参数: 生成10道题目,数值范围1-10

预期结果: 生成包含10道整数四则运算题目的文件

实际结果示例:

1. 3 + 5
2. 7 - 2
3. 4 * 6
4. 8 / 2
5. 9 + 1
6. 10 - 4
7. 2 * 3
8. 6 / 3
9. 5 + 4
10. 8 - 1

测试用例2: 生成包含分数的题目

python main.py -n 5 -r 10 --fractions

具体参数: 生成5道可能包含分数的题目,数值范围1-10

预期结果: 生成混合整数和分数的题目

实际结果示例:

1. 1/2 + 3/4
2. 2 * 5/6
3. 1'1/2 - 1/3
4. 7/8 / 1/2
5. 3 + 1/4

测试用例3: 生成题目并自动填写答案

python main.py -n 3 -r 5 --with-answers

具体参数: 生成3道题目,数值范围1-5,自动生成答案

预期结果: 生成题目文件和包含正确答案的答案文件

实际结果示例:

题目文件:

1. 2 + 3
2. 4 - 1
3. 2 * 2

答案文件:

1. 5
2. 3
3. 4

测试用例4: 答案校验(正确答案)

# 首先生成带答案的题目
python main.py -n 2 -r 3 --with-answers
# 然后使用相同的答案文件进行校验
python main.py -e questions.txt -a answers.txt

具体参数: 校验2道题目的答案

预期结果: 所有答案校验通过,显示100%正确

实际结果示例:

正在校验答案...
题目文件: questions.txt
答案文件: answers.txt

校验完成:
正确: 2 题 (100.0%)
错误: 0 题 (0.0%)
总计: 2 题
正确题目的编号: [1, 2]

校验结果已保存到: results.txt

测试用例5: 答案校验(错误答案)

# 生成题目后,手动修改answers.txt中的部分答案
python main.py -n 2 -r 3 --with-answers
# 修改answers.txt,将第二题的答案从2改为5
# 然后进行校验
python main.py -e questions.txt -a answers.txt

具体参数: 校验包含错误答案的答案文件

预期结果: 错误的答案被检测出来

实际结果示例:

正在校验答案...
题目文件: questions.txt
答案文件: answers.txt

校验完成:
正确: 1 题 (50.0%)
错误: 1 题 (50.0%)
总计: 2 题
正确题目的编号: [1]
错误题目的编号: [2]

校验结果已保存到: results.txt

测试用例6: 参数错误处理

python main.py -n 10  # 缺少range参数

具体参数: 只提供题目数量,未提供数值范围

预期结果: 显示参数错误提示

实际结果示例:

错误: 参数错误:当使用 -n 参数时,必须同时使用 -r 参数

测试用例7: 生成大量题目

python main.py -n 1000 -r 20

具体参数: 生成1000道题目,数值范围1-20

预期结果: 生成1000道不重复的题目,性能良好

实际结果: 成功生成1000道题目,平均生成速度约1000题/秒

测试用例8: 带括号的复杂表达式

python main.py -n 3 -r 5

具体参数: 生成3道可能包含括号的题目

预期结果: 生成带括号的复杂表达式

实际结果示例:

1. (2 + 3) * 4
2. 5 - (1 + 2)
3. (4 * 2) / (1 + 1)

测试用例9: 边界值测试(range=1)

python main.py -n 5 -r 1

具体参数: 生成5道题目,数值范围仅限0和1

预期结果: 生成使用0和1的合法题目

实际结果示例:

1. 1 + 0
2. 1 - 0
3. 1 * 1
4. 1 / 1
5. 0 + 1

测试用例10: 分数边界值测试

python main.py -n 3 -r 4 --fractions

具体参数: 生成3道包含分数的题目,范围较小

预期结果: 生成合法的分数运算题目

实际结果示例:

1. 1/2 + 1/4
2. 3/4 - 1/2
3. 1'1/3 * 3/4

项目小结

成功之处

  1. 模块化设计:项目采用清晰的模块化结构,各模块职责明确,便于维护和扩展。

  2. 功能完整:实现了题目生成、答案计算、答案校验等核心功能,并支持分数运算和括号。

  3. 代码质量:代码结构清晰,注释完善,遵循Python编码规范。

  4. 错误处理:包含全面的错误处理机制,对各种异常情况进行了合理处理。

经验教训

  1. 需求分析的重要性:在开发初期,我们应该更仔细地分析需求,特别是关于分数运算的规则。

  2. 测试的重要性:测试应该贯穿整个开发过程,而不仅是在开发完成后进行。

  3. 性能考虑:在设计算法时,应该提前考虑性能因素,特别是对于可能生成大量题目的情况。

结对感受

通过这次结对项目,我们不仅完成了一个功能完整的四则运算题目生成器,更重要的是学会了如何有效地进行团队合作。在开发过程中,我们互相学习、互相补充,遇到问题时共同探讨解决方案,大大提高了开发效率和代码质量。

闪光点分享

  1. 徐新曜:在算法设计方面表现出色,特别是在分数处理和括号插入算法上提出了很多创新的思路。

  2. 许国伟:在代码质量和错误处理方面非常细致,确保了程序的稳定性和健壮性。

未来改进方向

  1. 添加更多类型的题目,如小数运算、混合运算等
  2. 支持自定义题目难度
  3. 添加图形用户界面,使程序更易于使用
  4. 优化性能,支持生成更多题目的情况
  5. 添加更多的测试用例,提高代码覆盖率
posted @ 2025-10-21 22:38  X1erxes  阅读(4)  评论(0)    收藏  举报