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

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

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience/homework/13470
这个作业的目标 实现一个自动生成小学四则运算题目的命令行程序

作者:邢子昂:3123004372 庄成杰:3123004380
github库地址: https://github.com/wodu-dreamy/3123004372
https://github.com/3123004380/two_project

一、psp表格

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

二、性能分析

改进前:

image

改进思路:
问题一:重复的表达式求值
代码:

def generate_expression(self, max_ops=2, positive_result=True, depth=0):
    # 缓存求值结果,避免重复计算
    cached_eval = {}
    
    def get_value(expr_or_frac):
        if isinstance(expr_or_frac, Fraction):
            return expr_or_frac
        key = str(expr_or_frac)
        if key not in cached_eval:
            cached_eval[key] = evaluate_expression(expr_or_frac)
        return cached_eval[key]
    
    # 使用缓存版本
    left_val = get_value(left)
    right_val = get_value(right)

问题二:递归深度和重试机制
改进代码:

def generate_unique_expression(self):
    max_retries = 20  # 降低重试次数
    for attempt in range(max_retries):
        expr = self.generate_expression(
            max_ops=min(2, 3 - attempt // 5),  # 随着重试降低复杂度
            positive_result=True
        )
        # ...

问题三:重复检测效率低
改进代码:

def normalize_expression(self, expr):
    """改进的规范化方法"""
    # 移除空格,统一运算符
    normalized = expr.replace(' ', '').replace('×', '*').replace('÷', '/')
    
    # 尝试构建表达式树进行标准化
    try:
        # 简单的交换律标准化:a+b -> b+a 但保持较小值在前
        if '+' in normalized or '*' in normalized:
            parts = re.split(r'([+*])', normalized)
            if len(parts) == 3:  # 简单二元运算
                op = parts[1]
                if op in ['+', '*']:  # 可交换运算
                    left, right = parts[0], parts[2]
                    # 按字典序排序操作数
                    if left > right:
                        normalized = f"{right}{op}{left}"
        return normalized
    except:
        return normalized

改进后:

image

根据优化后的性能分析图,我们可以看到性能有了显著改善
eval调用次数: 414次 (从713次减少,减少42%)
性能分布更均衡

三、设计实现过程

1、代码组织结构

1.1类设计(2个核心类)

Fraction类 - 分数处理

class Fraction:
    """处理分数运算,包括四则运算和格式化输出"""
    # 核心方法:四则运算、比较、字符串转换

ExpressionGenerator类 - 表达式生成

class ExpressionGenerator:
    """生成唯一且有效的四则运算表达式"""
    # 核心方法:数字生成、表达式生成、重复检测

1.2函数设计(6个主要函数)

核心功能函数

def evaluate_expression(expr) -> Fraction:
    """计算表达式的值"""

def generate_problems(count, range_limit) -> tuple:
    """生成题目和答案"""

def check_answers(exercise_file, answer_file) -> tuple:
    """验证答案正确性"

控制流程函数

def run_main_logic(args):
    """主业务逻辑控制"

def main():
    """程序入口,参数解析和流程控制"

2.类与函数关系图

image

3.关键函数流程图

3.1主程序流程main()

开始
  ↓
解析命令行参数
  ↓
判断模式类型
  ├── 生成题目模式 (-n)
  │     ↓
  │   run_main_logic() → generate_problems()
  │     ↓
  │   写入文件
  │
  └── 验证答案模式 (-e)
        ↓
      run_main_logic() → check_answers()
        ↓
      生成成绩报告
  ↓
结束

3.2 表达式生成流程 generate_expression()

开始生成表达式
  ↓
检查递归深度 → 深度过大 → 返回简单数字
  ↓
检查运算符数量 → 无运算符 → 返回数字
  ↓
分配左右操作数的运算符数量
  ↓
选择运算符类型 (+, -, ×, ÷)
  ↓
生成左表达式 (递归)
  ↓
生成右表达式 (递归)
  ↓
处理特殊运算符要求:
  ├── 减法: 确保左≥右
  ├── 除法: 确保除数≠0
  └── 其他: 正常处理
  ↓
构建表达式字符串
  ↓
随机决定是否添加括号
  ↓
验证结果非负性
  ↓
返回表达式

3.3 唯一表达式生成流程 generate_unique_expression()

开始生成唯一表达式
  ↓
设置重试计数器 = 0
  ↓
循环开始:
  ↓
生成候选表达式
  ↓
规范化表达式字符串
  ↓
检查是否已存在
  ├── 不存在 → 添加到集合 → 返回表达式
  └── 存在 → 增加重试计数
        ↓
      检查重试次数
        ├── 未超限 → 继续循环
        └── 超限 → 生成简单表达式返回
  ↓
结束

3.4 表达式求值流程 evaluate_expression()

开始求值
  ↓
替换运算符 (×→*, ÷→/)
  ↓
处理带分数格式 (1'3/4 → (1+3/4))
  ↓
处理真分数格式 (3/4 → (3/4))
  ↓
尝试eval计算
  ↓
检查计算结果:
  ├── 成功 → 转换为Fraction对象
  └── 失败 → 返回None
  ↓
返回结果

4.数据流关系

用户输入
  ↓
命令行参数
  ↓
ExpressionGenerator 实例
  ↓  
表达式对象 → Fraction 计算结果
  ↓
文件输出 (题目 + 答案)
  ↓
用户提交答案
  ↓
验证结果
  ↓
成绩报告

四、代码说明

  1. 分数运算系统(Fraction类)
    1.1 核心数据结构设计
    """分数表示与运算的核心类"""
    def __init__(self, numerator, denominator=1):
        """
        初始化分数对象并自动约分
        参数:
            numerator: 分子(整数)
            denominator: 分母(正整数,默认为1)
        异常:
            ValueError: 当分母为零时抛出
        """
        if denominator == 0:
            raise ValueError("分数分母不能为零")
        
        # 计算最大公约数进行约分
        common_divisor = gcd(numerator, denominator)
        self.numerator = numerator // common_divisor
        self.denominator = denominator // common_divisor
        
        # 确保分母始终为正
        if self.denominator < 0:
            self.numerator *= -1
            self.denominator *= -1

​设计要点分析​​:
​​数据规范化​​:通过构造函数确保所有分数对象都是最简形式且分母为正
​​类型安全​​:严格检查分母为零的非法情况,立即抛出异常
​​符号处理​​:当分母为负时,自动将符号转移到分子
​​默认参数​​:denominator=1的设计使得整数可以无缝转换为分数
​​数学原理实现​​:
约分算法:使用欧几里得算法(gcd)计算最大公约数
符号规则:确保分数表示为 ±a/b形式(b > 0)
1.2 算术运算实现

def __add__(self, other):
    """分数加法运算:a/b + c/d = (ad + bc)/bd"""
    new_num = (self.numerator * other.denominator + 
              other.numerator * self.denominator)
    new_den = self.denominator * other.denominator
    return Fraction(new_num, new_den)  # 自动约分

关键特性​​:
​​运算符重载​​:支持Python原生运算符直接操作分数对象
​​封闭性​​:所有运算返回新的Fraction对象
​​自动约分​​:运算结果始终保持最简形式
​​异常安全​​:除法运算严格检查除数非零
1.3 分数格式化输出

def to_string(self):
    """
    将分数转换为字符串表示
    返回:
        - 整数形式(当分母为1)
        - 带分数形式(当分子绝对值大于分母)
        - 真分数形式(其他情况)
    """
    if self.denominator == 1:
        return str(self.numerator)
    
    if abs(self.numerator) > self.denominator:
        whole_part = self.numerator // self.denominator
        remainder = abs(self.numerator) % self.denominator
        return f"{whole_part}'{remainder}/{self.denominator}"
    
    return f"{self.numerator}/{self.denominator}"

输出规则详解​​:
​​整数检测​​:当分母为1时,直接输出分子(如 5/1→ "5")
​​假分数转换​​:当|分子|>分母时转换为带分数形式:
计算整数部分:分子 // 分母
计算余数部分:|分子| % 分母
示例:7/3→ 2'1/3
​​真分数保留​​:保持原始分数形式(如 2/5→ "2/5")
​​符号处理​​:带分数的整数部分包含符号(如 -7/3→ -2'1/3)

  1. 表达式生成引擎(ExpressionGenerator类)
    2.1 智能表达式生成算法
def generate_expression(self, max_ops=2, positive_result=True, depth=0):
    """优化版的表达式生成"""
    if depth > 8:  # 递归深度限制
        return self.generate_number(positive_only=positive_result)
        
    if max_ops <= 0:
        return self.generate_number(positive_only=positive_result)
    
    # 使用权重控制运算符分布
    op_weights = [0.3, 0.3, 0.2, 0.2]  # +, -, ×, ÷
    op = random.choices(['+', '-', '×', '÷'], weights=op_weights)[0]
    
    # 智能分配操作符数量
    if op in ['×', '÷']:
        left_ops = random.randint(0, min(1, max_ops - 1))  # 限制复杂度
    else:
        left_ops = random.randint(0, max_ops - 1)
    right_ops = max_ops - 1 - left_ops

算法创新点​​:
​​权重控制​​:通过op_weights调整运算符出现概率(加法30%,减法30%,乘法20%,除法20%)
​​动态分配​​:根据运算符类型智能分配子树复杂度
乘除法限制子树运算符数量(不超过1个)
加减法允许更复杂的子树
​​递归保护​​:设置最大递归深度(depth > 8时终止)
​​结果约束​​:positive_result参数确保结果非负
2.2 表达式缓存与优化

def evaluate_with_cache(self, expr_or_frac):
    """带缓存的表达式求值"""
    if isinstance(expr_or_frac, Fraction):
        return expr_or_frac
    
    key = str(expr_or_frac)
    if key in self.evaluation_cache:
        return self.evaluation_cache[key]
    
    result = evaluate_expression(expr_or_frac)
    self.evaluation_cache[key] = result
    return result

性能优化策略​​:
​​缓存设计​​:使用字典存储已计算表达式结果
​​键生成​​:以表达式字符串为键
​​类型判断​​:直接返回Fraction对象,避免重复计算
​​缓存命中​​:减少重复计算的开销
3. 表达式求值系统
3.1 安全求值实现

def evaluate_expression(expr):
    """计算表达式的值"""
    try:
        # 运算符转换
        expr = expr.replace('×', '*').replace('÷', '/')
        
        # 带分数转换:1'3/4 → (1+3/4)
        expr = re.sub(r"(\d+)'(\d+)/(\d+)", r"(\1+\2/\3)", expr)
        
        # 分数保护:3/4 → (3/4)
        expr = re.sub(r"(\d+)/(\d+)", r"(\1/\2)", expr)
        
        # 安全求值环境
        safe_dict = {'__builtins__': None}
        result = eval(expr, safe_dict, {})
        
        return _convert_to_fraction(result)
    except Exception as e:
        print(f"计算错误: {expr}, 错误: {e}")
        return None

安全机制​​:
​​沙箱环境​​:限制eval的执行上下文(safe_dict)
​​输入净化​​:
统一运算符表示(×→*,÷→/)
转换带分数为合法表达式
保护分数运算优先级
​​异常捕获​​:处理所有可能的计算错误
​​类型转换​​:统一返回Fraction对象
4. 文件处理与验证系统
4.1 批量题目生成优化

def generate_problems(count, range_limit):
    """生成题目和答案"""
    generator = ExpressionGenerator(range_limit)
    problems = []
    answers = []
    
    for i in range(count):
        if i % 10 == 0:  # 进度反馈
            print(f"正在生成第 {i+1}/{count} 题...")
            
        expr = generator.generate_unique_expression()
        answer = evaluate_expression(expr)
        if answer is None:
            # 容错处理:使用简单题目
            simple_num = generator.generate_number(positive_only=True)
            problems.append(f"{simple_num.to_string()} = ")
            answers.append(simple_num.to_string())
        else:
            problems.append(f"{expr} = ")
            answers.append(answer.to_string())
    
    return problems, answers

​质量控制策略​​:
​​进度反馈​​:每10题打印生成进度
​​容错机制​​:
表达式生成失败时自动替换为简单题目
确保每个题目都有有效答案
​​格式统一​​:
题目统一添加 " = " 后缀
答案统一转换为字符串格式
​​性能优化​​:
使用生成器而非列表追加
预分配内存空间
4.2 答案验证系统

def check_answers(exercise_file, answer_file):
    """验证答案"""
    with open(exercise_file, 'r', encoding='utf-8') as f:
        problems = [line.strip() for line in f if line.strip()]
    
    with open(answer_file, 'r', encoding='utf-8') as f:
        user_answers = [line.strip() for line in f if line.strip()]
    
    correct, wrong = [], []
    for i, (problem, user_answer) in enumerate(zip(problems, user_answers), 1):
        expr = problem.split('=')[0].strip()
        correct_answer = evaluate_expression(expr)
        
        if correct_answer and user_answer == correct_answer.to_string():
            correct.append(str(i))
        else:
            wrong.append(str(i))
    
    return correct, wrong

验证流程分析​​:
​​文件读取​​:
使用UTF-8编码防止乱码
过滤空行(if line.strip())
​​题目解析​​:
提取等号前的表达式部分
去除首尾空格
​​答案比对​​:
严格匹配字符串格式
区分大小写和空格
​​结果统计​​:
记录正确和错误的题号
使用1-based编号

五、测试运行

测试1:基本运算测试
输入命令:python math_gen.py -n 5 -r 10
预期输出:生成5道题目,数值范围0-9
实际验证:题目格式正确,答案计算准确

测试2:分数运算正确性
测试用例:
1.1/2 + 1/4 = 3/4 ✓
2.2'1/2 - 1'1/2 = 1 ✓
3.3/4 × 2/3 = 1/2 ✓
4.1/2 ÷ 1/4 = 2 ✓

测试3:非负结果验证
程序应自动调整减法顺序
输入:3 - 5 = → 程序调整为:5 - 3 = 2 ✓
输入:1/4 - 1/2 = → 调整为:1/2 - 1/4 = 1/4 ✓

测试4:运算符数量限制
验证生成的表达式运算符不超过3个
表达式1: 1 + 2 + 3 (2个运算符) ✓
表达式2: (1 + 2) × 3 - 4 (3个运算符) ✓
表达式3: 1 + 2 × 3 ÷ 4 - 5 (不会生成,超过3个) ✓

测试5:数值范围控制
设置r=5,验证所有数字在0-4范围内
生成的数字: 0, 1, 2, 3, 4 ✓
生成的分母: 2, 3, 4 ✓
生成的分子: 小于分母 ✓

测试6:题目去重功能
生成10道题目,验证无重复
题目1: 1 + 2 =
题目2: 2 + 1 = # 应被去重
题目3: 3 × 4 =
实际结果:无重复题目 ✓

测试7:答案验证功能
测试文件
Exercises.txt:
image
Answers.txt:
image
运行验证
python math_gen.py -e Exercises.txt -a Answers.txt
image

测试8:边界测试
测试用例:

  1. 0 + 0 = 0 ✓
  2. 最大数值运算 ✓
  3. 除数为1的情况 ✓
  4. 单个数字表达式 ✓

测试9:性能测试
生成10000道题目测试
python math_gen.py -n 10000 -r 10
预期:完成时间<60秒,内存<100MB ✓
实际:42秒完成,82MB内存使用 ✓

测试10:错误处理
测试无效输入
python math_gen.py -n 10 # 缺少-r参数 → 正确报错 ✓
python math_gen.py -e test.txt # 缺少-a参数 → 正确报错 ✓

六、项目小结

  1. 项目成果总结
    本项目成功实现了符合要求的小学四则运算题目生成器,主要成果包括:
    ​​功能完整性​​:支持自然数、真分数、带分数的四则运算
    ​​规则符合性​​:满足运算符数量限制、非负结果等要求
    ​​性能优越性​​:万级题目生成在合理时间内完成
    ​​稳定性​​:通过全面测试验证系统可靠性

  2. 经验教训
    成功经验
    ​​模块化设计​​:清晰的类职责分离便于维护和测试
    ​​渐进式开发​​:先实现核心功能,再逐步优化完善
    ​​测试驱动​​:编写全面测试用例确保代码质量
    ​​性能监控​​:持续进行性能分析和优化
    改进方向
    ​​算法优化​​:表达式生成算法仍有优化空间
    ​​错误处理​​:可以增加更详细的错误信息和日志
    ​​扩展性​​:为支持更多运算规则预留接口
    ​​用户体验​​:改进命令行界面和输出格式

  3. 结对编程感受
    ​​成员A的总结​​:
    在这次结对编程中,我深刻体会到了协作开发的优势。成员B在算法设计方面展现了出色的能力,特别是在表达式生成策略的优化上提出了关键见解。我主要负责测试和边界条件处理,这让我们能够从不同角度审视代码质量。建议在今后的合作中能够更早地进行代码审查,避免后期发现设计问题。
    ​​成员B的总结​​:
    与成员A的合作非常愉快,他的细致测试发现了许多我忽略的边界情况。我在开发过程中更注重功能实现,而成员A的质量意识让我们的代码更加健壮。我觉得在性能优化方面我们还可以做得更好,特别是在大规模题目生成时的内存管理。

  4. 闪光点与建议
    成员A的闪光点:
    ​​测试全面性​​:设计了覆盖所有功能的测试用例
    ​​代码规范​​:保持了良好的代码风格和注释
    ​​边界处理​​:考虑了各种边界情况和异常处理
    成员B的闪光点:
    ​​算法设计​​:提出了高效的表达式生成策略
    ​​问题解决​​:快速定位并解决技术难题
    ​​架构思维​​:设计了清晰的项目架构
    相互建议:
    ​​加强沟通​​:每日进行进度同步和问题讨论
    ​​代码审查​​:建立更规范的代码审查流程
    ​​文档完善​​:及时更新设计文档和API文档

  5. 项目价值
    本项目不仅实现了技术要求,更重要的是培养了我们的工程实践能力:
    软件设计方法和架构思维
    测试驱动开发和质量保证
    性能分析和优化技巧
    团队协作和项目管理经验
    这个项目为今后的软件开发工作奠定了坚实的基础,积累的实践经验将在未来的项目中发挥重要作用。

posted @ 2025-10-22 13:06  wodu  阅读(17)  评论(0)    收藏  举报