结对项目

一、作业基本信息

项目 内容
这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/homework/13479
这个作业的目标 实现一个小自动生成小学四则运算题目的程序
github仓库链接 https://github.com/OcOGUA/basic_problem
成员1:罗天乐 3123004800
成员2:李家晋 3123004790

二、PSP表格

PSP Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 30 30
· Estimate · 估计任务时间 30 30
Development 开发 410 450
· Analysis · 需求分析(含学习新技术) 60 90
· Design Spec · 生成设计文档 60 50
· Design Review · 设计复审(同伴评审) 30 30
· Coding Standard · 代码规范(制定/遵循) 20 30
· Design · 具体设计(模块划分、算法设计) 60 70
· Coding · 具体编码 90 80
· Code Review · 代码复审(自审+工具检查) 30 40
· Test · 测试(单元测试+集成测试+性能测试) 60 60
Reporting 报告 90 100
· Test Report · 测试报告(覆盖率、性能分析) 40 40
· Size Measurement · 计算工作量(代码行数、文档页数) 20 30
· Postmortem & Process Improvement Plan · 事后总结与过程改进计划 30 30
合计 530 580

三、计算模块接口部分的性能改进

1.工具准备与环境配置

工具用途 专业工具 安装命令 核心作用
函数级性能分析 cProfile 内置模块 记录函数调用次数、耗时、调用关系
可视化分析结果 snakeviz pip install snakeviz 将cProfile数据转为交互式调用图
行级性能定位 line_profiler pip install line_profiler 逐行分析函数耗时占比

2.生成性能报告

运行程序,并使用 cProfile 分析、用snakeviz可视化调用关系

3.高消耗函数分析与改进思路

3.1性能数据

python 复制代码
main.py:29(generate_exercises)      0.0915 s
generator.py:15(generate_expression) 0.0866 s  
evaluator.py:11(parse_and_evaluate)  0.0397 s
evaluator.py:40(_evaluate_infix)     0.0381 s

3.2高消耗函数分析

3.2.1 _evaluate_infix (0.0381s) - 最热函数

调用路径
generate_exercisesgenerate_expression_evaluate_expression_stringparse_and_evaluate_evaluate_infix

性能问题

  • 每次表达式生成都要调用完整的调度场算法
  • 复杂的字符串分词和解析逻辑
  • 操作符栈和值栈的频繁操作

代码

python 复制代码
def _evaluate_infix(expression):
    tokens = ExpressionEvaluator._tokenize(expression)  # 分词开销
    values = []
    operators = []
    
    # 完整的调度场算法,O(n)复杂度但常数很大
    for token in tokens:
        if ExpressionEvaluator._is_number(token):
            num = ExpressionEvaluator._parse_number(token)  # 重复解析
            values.append(num)
        # ... 复杂的栈操作逻辑
3.2.2 parse_and_evaluate (0.0397s)

性能问题

  • 包装了 _evaluate_infix 的所有开销
  • 额外的字符串预处理(移除等号和空格)
  • 异常处理开销
3.2.3 generate_expression (0.0866s)

性能问题

python 复制代码
def generate_expression(self, max_operators=3):
    attempts = 0
    while attempts < 100:  # 可能循环100次!
        # 构建表达式
        expr_parts = []
        first_num = generate_random_fraction(self.max_range)
        expr_parts.append(str(first_num))  # 字符串转换
        
        for _ in range(num_operators):
            operator = random.choice(self.operators)
            operand = generate_random_fraction(self.max_range)
            expr_parts.append(operator)
            expr_parts.append(str(operand))  # 更多字符串转换
        
        expr_str = ' '.join(expr_parts)  # 字符串拼接
        
        # 每次尝试都要完整求值!
        result = self._evaluate_expression_string(expr_str)

主要开销

  • 高频调用求值器 :每次尝试都要调用 _evaluate_expression_string
  • 字符串操作爆炸 :大量的 str() 转换和拼接
  • 重复的分数生成:可能生成相同的分数多次
3.2.4 generate_exercises (0.0915s)

性能问题

  • 循环调用 generate_expression N 次(题目数量)
  • 每个表达式都可能经历多次尝试(最多100次)
  • 文件写入操作

3.3 代码改进思路

3.3.1 智能重复检测系统
python 复制代码
# 两级重复检测机制
def generate_expression(self, max_operators=3):
    # 1. 快速哈希签名检测
    signature = self._expression_signature(expr_str)
    if signature in self.generated_signatures:
        continue
        
    # 2. 规范形式检测(处理交换律)
    canonical_form = self._get_canonical_form(expr_str)
    if canonical_form in self.generated_canonical_forms:
        continue

改进效果

  • 哈希比较 O(1) 替代字符串比较 O(n)
  • 检测数学等价的表达式(如 a+b 与 b+a)
  • 大幅减少无效生成尝试

3.3.2 并行处理架构

python 复制代码
# checker.py 中的多线程优化
@staticmethod
def _check_answers_parallel(exercise_lines, answer_lines, batch_size):
    num_workers = min(cpu_count(), 4)
    with concurrent.futures.ThreadPoolExecutor(max_workers=num_workers) as executor:
        # 分批并行处理
        futures = []
        for i in range(0, len(tasks), batch_size):
            batch = tasks[i:i+batch_size]
            future = executor.submit(AnswerChecker._process_batch, batch)
            futures.append(future)

改进效果

  • 大批量答案检查时性能显著提升
  • 合理的线程数控制,避免资源竞争
  • 保持小批量时的串行效率

3.3.3 动态优化策略

python 复制代码
# 根据规模自适应调整
if len(self.generated_signatures) > 5000:
    max_attempts = 50  # 降低重复检测严格度
elif len(self.generated_signatures) > 10000:
    max_attempts = 20

if count > 10000:
    print("检测到大量题目生成请求,正在优化生成策略...")

改进效果

  • 不同规模下的最优策略
  • 避免"过度优化"小规模场景
  • 大规模生成时的性能保障

3.3.4 表达式规范化系统

python 复制代码
def _get_canonical_form(self, expr):
    """获取表达式的规范形式,处理交换律和结合律"""
    tokens = ExpressionEvaluator._tokenize(expr)
    tree = self._build_expression_tree(tokens)
    canonical_tree = self._canonicalize_tree(tree)  # 规范化处理
    return self._tree_to_string(canonical_tree)

改进效果

  • 检测数学等价而字符串不同的表达式
  • 提高题目多样性质量
  • 减少语义重复

3.4 改善结果

四、小学四则运算题目生成器模块的设计与实现流程

1.需求细化

1.1 输入输出规范

命令行参数支持:

  • 生成模式python main.py -n [题目数量] -r [数值范围]
    • 示例:python main.py -n 100 -r 20(生成100道数值范围0~20的题目)
  • 判题模式python main.py -e [题目文件路径] -a [答案文件路径]
    • 示例:python main.py -e ./Exercises.txt -a ./Answers.txt

文件读写规范:

  • 生成模式输出
    • Exercises.txt:题目文件,格式如 3 + 5/7 × 2'1/3 =
    • Answers.txt:答案文件,格式如 5'2/3
  • 判题模式输出
    • Grade.txt:评分报告,格式如 Correct: 8 (1,3,5...) Wrong: 2 (2,4...)
  • 编码要求:UTF-8,支持空文件和超大文件(10万题约10MB)

生成规则:

  • 运算符数量:1~3个(+、-、×、÷)
  • 数字类型:自然数、真分数、带分数
  • 合法性约束:
    • 减法结果非负
    • 除法结果为真分数
  • 题目去重:通过表达式标准化处理交换律

1.2 核心算法设计

生成流程:

参数解析 → 数字生成 → 运算符选择 → 合法性校验 → 表达式构建 → 结果计算 → 去重 → 文件输出

关键步骤实现:

  • 数字生成:随机选择生成各类数字
  • 合法性校验:模拟计算过程确保约束条件
  • 去重机制:表达式标准化处理交换律
  • 结果计算 :基于fractions.Fraction高精度计算

2. 架构设计与模块分析

2.1 模块架构对比

四则运算系统模块架构表

模块文件 核心函数/类 功能描述
main.py main() 程序主入口,调度整个流程
generate_exercises(count, range_limit) 生成指定数量和范围的题目
check_answers(exercise_file, answer_file) 检查答案并生成成绩报告
generate_simple_exercise(range_limit) 生成简单备选题目
utils.py parse_arguments() 解析命令行参数,校验合法性
write_exercises_and_answers(exercises, answers) 写入题目和答案到文件
generator.py ExpressionGenerator 表达式生成器主类
generate_expression(max_operators=3) 生成算术表达式
_get_canonical_form(expr) 获取表达式规范形式(去重)
_build_expression_tree(tokens) 构建表达式树
_canonicalize_tree(tree) 规范化表达式树
_expression_signature(expr) 生成表达式签名
fraction.py MyFraction 自定义分数类
generate_random_fraction(max_range) 随机生成数字/分数
parse(s) 字符串转Fraction对象
to_improper_fraction() 转换为假分数形式
evaluator.py ExpressionEvaluator 表达式求值器
parse_and_evaluate(expression) 解析并计算表达式
_evaluate_infix(expression) 计算中缀表达式
_tokenize(expression) 表达式标记化
_apply_operator(operator, val1, val2) 应用运算符计算
format_result(result) 格式化结果为要求形式
checker.py AnswerChecker 答案检查器
check_answers(exercise_file, answer_file) 检查答案文件正确性
_check_answers_parallel(exercise_lines, answer_lines) 并行检查答案
_process_batch(batch) 处理批次数检查
write_grade_file(correct_list, wrong_list) 生成评分报告
duplicate_test.py test_commutative_duplication() 测试交换律重复检测
test_associative_difference() 测试结合律差异
test_complex_cases() 测试复杂情况

3. 核心算法实现分析

3.1 表达式生成流程

python 复制代码
# generate_expression(max_operators=3) 函数流程
开始
↓
设置尝试次数 attempts = 0, max_attempts = 100
↓
生成运算符数量: num_operators = random.randint(1, min(max_operators, 3))
↓
生成第一个操作数: first_num = generate_random_fraction(self.max_range)
↓
循环 num_operators 次:
    ↓
    随机选择运算符: operator = random.choice(['+', '-', '*', '/'])
    ↓
    生成操作数: operand = generate_random_fraction(self.max_range)
    ↓
    构建表达式部分列表
↓
组装完整表达式字符串: expr_str = ' '.join(expr_parts)
↓
生成表达式签名: signature = self._expression_signature(expr_str)
↓
生成规范形式: canonical_form = self._get_canonical_form(expr_str)
↓
调用 check_expression_validity: 检查签名和规范形式是否重复
↓
计算表达式结果: result = ExpressionEvaluator.parse_and_evaluate(expr_str)
↓
验证约束条件: result ≥ 0 且减法不产生负数
↓
保存到已生成集合,返回表达式和答案
↓
如果达到最大尝试次数,调用 _generate_simple_expression() 生成备选
结束

# _get_canonical_form(expr) 规范形式生成流程
开始
↓
预处理表达式: expr = expr.replace('=', '').strip()
↓
词法分析: tokens = ExpressionEvaluator._tokenize(expr)
↓
构建表达式树: tree = self._build_expression_tree(tokens)
↓
规范化表达式树: canonical_tree = self._canonicalize_tree(tree)
↓
对于 '+' 和 '*' 运算符:
    ↓
    获取左右子树字符串表示
    ↓
    按字符串排序操作数: 确保左 ≤ 右
↓
树转字符串: return self._tree_to_string(canonical_tree)
↓
如果解析失败,调用 _simple_canonical_form(expr) 备选方案
结束

3.2 关键算法实现

python 复制代码
# parse_and_evaluate(expression) 表达式求值流程
开始
↓
预处理表达式: expression = expression.replace('=', '').strip()
↓
词法分析: tokens = ExpressionEvaluator._tokenize(expression)
↓
初始化值栈 values = [] 和运算符栈 operators = []
↓
遍历 tokens:
    ↓
    如果是数字: 调用 _parse_number(token) 解析为 Fraction,压入 values
    ↓
    如果是 '(': 压入 operators
    ↓
    如果是 ')': 弹出 operators 直到 '(',并计算子表达式
    ↓
    如果是运算符: 根据优先级处理运算符栈
↓
处理剩余运算符栈中的运算符
↓
返回 values[0] 作为最终结果
结束


# check_answers(exercise_file, answer_file) 答案检查流程
开始
↓
读取题目文件: exercise_lines = ef.readlines()
↓
读取答案文件: answer_lines = af.readlines()
↓
如果题目数量 > 5000: 调用 _check_answers_parallel 并行处理
↓
否则顺序处理每道题目:
    ↓
    提取题目编号和表达式
    ↓
    提取答案编号和用户答案
    ↓
    调用 ExpressionEvaluator.parse_and_evaluate(expression) 计算标准答案
    ↓
    格式化结果: formatted_result = ExpressionEvaluator.format_result(calculated_result)
    ↓
    比较 formatted_result == user_answer
    ↓
    添加到 correct_list 或 wrong_list
↓
调用 write_grade_file(correct_list, wrong_list) 生成成绩报告
结束

# generate_random_fraction(max_range) 数字生成流程
开始
↓
随机决定类型: if random.random() < 0.4 → 生成整数,否则生成分数
↓
如果是整数: return MyFraction(whole_number=random.randint(0, max_range-1))
↓
如果是分数:
    ↓
    生成分母: denominator = random.randint(2, max_range-1)
    ↓
    生成分子: numerator = random.randint(1, denominator-1)
    ↓
    决定是否带分数: if random.random() < 0.3 and max_range > 2
        ↓
        生成整数部分: whole_number = random.randint(0, max_range-2)
        ↓
        return MyFraction(numerator, denominator, whole_number)
    ↓
    否则: return MyFraction(numerator, denominator)
结束

4.函数关系与调用流程

4.1关键函数调用关系

主流程: main → generate_exercises/check_answers

生成流程: generate_expression → 重复检测 → 求值验证

重复检测: _get_canonical_form → 分词 → 建树 → 规范化 → 树转字符串

求值流程: parse_and_evaluate → 分词 → 调度场算法 → 应用运算符

数字生成: generate_random_fraction → MyFraction 构造

答案检查: check_answers → 并行/顺序处理 → 格式比较 → 成绩输出

4.2 函数流程图

1. 总体函数调用层次

2.主程序执行流程

3. 表达式生成流程

4.表达式求值流程

5.答案检查流程

6.分数处理流程

7.重复检测流程

五、核心代码说明

1.generator.py - 表达式生成器

主要功能: 生成不重复的合法算术表达式,处理重复检测和约束验证

python 复制代码
import random
from fraction import MyFraction, generate_random_fraction
from evaluator import ExpressionEvaluator
from collections import Counter


class ExpressionGenerator:
    """
    算术表达式生成器
    """

    def __init__(self, max_range):
        self.max_range = max_range
        self.operators = ['+', '-', '*', '/']
        # 使用更高效的数据结构存储已生成的表达式指纹
        self.generated_signatures = set()
        self.generated_canonical_forms = set()
        # 控制重复检测策略,当生成数量较大时降低重复检测频率
        self.duplication_check_enabled = True

    def generate_expression(self, max_operators=3):
        """
        生成算术表达式
        :param max_operators: 最大运算符数量
        :return: (表达式字符串, 答案)
        """
        attempts = 0
        max_attempts = 100
        
        # 当已生成大量题目时,适当放宽重复检测
        if len(self.generated_signatures) > 5000:
            max_attempts = 50
        elif len(self.generated_signatures) > 10000:
            max_attempts = 20
            
        while attempts < max_attempts:  # 限制尝试次数
            # 随机确定运算符数量 (1-3个)
            num_operators = random.randint(1, min(max_operators, 3))
            
            # 构建表达式
            expr_parts = []
            
            # 添加第一个操作数
            first_num = generate_random_fraction(self.max_range)
            expr_parts.append(str(first_num))
            
            # 添加运算符和操作数
            for _ in range(num_operators):
                operator = random.choice(self.operators)
                operand = generate_random_fraction(self.max_range)
                
                expr_parts.append(operator)
                expr_parts.append(str(operand))
            
            expr_str = ' '.join(expr_parts)
            
            # 使用签名而不是完整表达式来检测重复,提高效率
            signature = self._expression_signature(expr_str)
            
            # 检查简单哈希重复
            if self.duplication_check_enabled and signature in self.generated_signatures:
                attempts += 1
                continue
            
            # 检查规范形式重复(处理交换律和结合律)
            canonical_form = self._get_canonical_form(expr_str)
            if self.duplication_check_enabled and canonical_form in self.generated_canonical_forms:
                attempts += 1
                continue
            
            # 计算表达式结果
            try:
                result = self._evaluate_expression_string(expr_str)
                # 检查是否有负数结果
                if result < 0:
                    attempts += 1
                    continue
                
                # 对减法运算,确保不会产生负数
                if not self._check_subtraction(expr_parts):
                    attempts += 1
                    continue
                    
                # 表达式有效,添加到已生成集合
                self.generated_signatures.add(signature)
                self.generated_canonical_forms.add(canonical_form)
                result_fraction = MyFraction.from_fraction(result)
                return f"{expr_str} =", str(result_fraction)
            except (ZeroDivisionError, ValueError):
                attempts += 1
                continue
        
        # 如果达到最大尝试次数仍未生成有效表达式,则生成一个简单的表达式
        simple_expr = self._generate_simple_expression()
        signature = self._expression_signature(simple_expr)
        canonical_form = self._get_canonical_form(simple_expr)
        self.generated_signatures.add(signature)
        self.generated_canonical_forms.add(canonical_form)
        return simple_expr

    def _check_subtraction(self, expr_parts):
        """
        检查减法运算是否会产生负数
        :param expr_parts: 表达式部分列表
        :return: True表示不会产生负数,False表示会产生负数
        """
        # 简化处理,暂时总是返回True
        # 实际应用中需要更复杂的逻辑来检查减法运算
        return True

    def _evaluate_expression_string(self, expression):
        """
        计算表达式字符串的值
        :param expression: 表达式字符串
        :return: 计算结果(Fraction类型)
        """
        # 导入表达式求值器
        result = ExpressionEvaluator.parse_and_evaluate(expression)
        return result

    def _expression_signature(self, expr):
        """
        生成表达式的签名用于快速重复检测
        :param expr: 表达式
        :return: 表达式签名
        """
        # 移除空格并将字符排序,形成统一的签名
        # 这种方法比完整的标准化更快,且能有效避免大多数重复
        cleaned = ''.join(expr.split())
        return hash(cleaned)  # 使用hash提高查找效率

    def _get_canonical_form(self, expr):
        """
        获取表达式的规范形式,用于检测通过交换律和结合律可以转换的等价表达式
        :param expr: 表达式字符串
        :return: 表达式的规范形式
        """
        # 移除等号和空格
        expr = expr.replace('=', '').strip()
        
        # 解析表达式为树形结构并规范化
        try:
            tokens = ExpressionEvaluator._tokenize(expr)
            tree = self._build_expression_tree(tokens)
            canonical_tree = self._canonicalize_tree(tree)
            return self._tree_to_string(canonical_tree)
        except Exception:
            # 如果解析失败,回退到简单的规范化方法
            return self._simple_canonical_form(expr)

    def _build_expression_tree(self, tokens):
        """
        构建表达式树
        :param tokens: 标记列表
        :return: 表达式树
        """
        # 使用调度场算法的逆过程构建表达式树
        values = []
        operators = []
        
        precedence = {'+': 1, '-': 1, '*': 2, '/': 2}
        
        i = 0
        while i < len(tokens):
            token = tokens[i]
            if ExpressionEvaluator._is_number(token):
                values.append(('num', token))
            elif token == '(':
                operators.append(token)
            elif token == ')':
                while operators and operators[-1] != '(':
                    op = operators.pop()
                    if len(values) >= 2:
                        right = values.pop()
                        left = values.pop()
                        values.append(('op', op, left, right))
                if operators:
                    operators.pop()  # 移除 '('
            elif token in precedence:
                while (operators and operators[-1] != '(' and
                       operators[-1] in precedence and
                       precedence[operators[-1]] >= precedence[token]):
                    op = operators.pop()
                    if len(values) >= 2:
                        right = values.pop()
                        left = values.pop()
                        values.append(('op', op, left, right))
                operators.append(token)
            i += 1
        
        while operators:
            op = operators.pop()
            if len(values) >= 2:
                right = values.pop()
                left = values.pop()
                values.append(('op', op, left, right))
        
        return values[0] if values else ('num', '0')

    def _canonicalize_tree(self, tree):
        """
        规范化表达式树,处理交换律
        :param tree: 表达式树
        :return: 规范化的表达式树
        """
        if tree[0] == 'num':
            return tree
        
        op = tree[1]
        left = self._canonicalize_tree(tree[2])
        right = self._canonicalize_tree(tree[3])
        
        # 对于可交换的运算符(+和*),按操作数的字符串表示排序
        if op in ['+', '*']:
            left_str = self._tree_to_string(left)
            right_str = self._tree_to_string(right)
            if left_str > right_str:
                return ('op', op, right, left)
            else:
                return ('op', op, left, right)
        else:
            return ('op', op, left, right)

    def _tree_to_string(self, tree):
        """
        将表达式树转换为字符串
        :param tree: 表达式树
        :return: 表达式字符串
        """
        if tree[0] == 'num':
            return tree[1]
        else:
            op = tree[1]
            left = self._tree_to_string(tree[2])
            right = self._tree_to_string(tree[3])
            return f"({left}{op}{right})"

    def _simple_canonical_form(self, expr):
        """
        简单的规范化方法,作为备选方案
        :param expr: 表达式字符串
        :return: 规范化后的表达式
        """
        # 移除空格
        cleaned = ''.join(expr.split())
        # 提取操作数并排序(仅对纯加法或纯乘法表达式有效)
        if all(op in ['+', '(' ,')'] or ExpressionEvaluator._is_number(op) for op in cleaned):
            numbers = [token for token in ExpressionEvaluator._tokenize(expr) 
                      if ExpressionEvaluator._is_number(token)]
            numbers.sort()
            return '+'.join(numbers)
        elif all(op in ['*', '(' ,')'] or ExpressionEvaluator._is_number(op) for op in cleaned):
            numbers = [token for token in ExpressionEvaluator._tokenize(expr) 
                      if ExpressionEvaluator._is_number(token)]
            numbers.sort()
            return '*'.join(numbers)
        else:
            return cleaned

    def _generate_simple_expression(self):
        """
        生成一个简单的表达式作为备选
        :return: 简单表达式字符串
        """
        op = random.choice(['+', '*'])
        num1 = generate_random_fraction(self.max_range)
        num2 = generate_random_fraction(self.max_range)
        
        return f"{num1} {op} {num2} ="

    def enable_duplication_check(self, enabled):
        """
        启用或禁用重复检测
        :param enabled: 是否启用重复检测
        """
        self.duplication_check_enabled = enabled

    def get_generated_count(self):
        """
        获取已生成的表达式数量
        :return: 已生成的表达式数量
        """
        return len(self.generated_signatures)

2. evaluator.py - 表达式求值器

主要功能: 精确计算算术表达式的结果,支持分数运算和运算符优先级处理

python 复制代码
from fractions import Fraction
import re
from fraction import MyFraction


class ExpressionEvaluator:
    """
    表达式求值器
    """

    @staticmethod
    def parse_and_evaluate(expression):
        """
        解析并计算表达式
        :param expression: 表达式字符串
        :return: 计算结果
        """
        # 移除等号和空格
        expression = expression.replace('=', '').strip()
        
        # 使用调度场算法解析表达式
        try:
            result = ExpressionEvaluator._evaluate_infix(expression)
            return result
        except Exception as e:
            raise ValueError(f"表达式计算错误: {str(e)}")

    @staticmethod
    def _tokenize(expression):
        """
        将表达式分解为标记
        :param expression: 表达式字符串
        :return: 标记列表
        """
        # 使用正则表达式匹配数字、分数和运算符
        pattern = r"(\d+'?\d*/\d+|\d+|\+|\-|\*|\/|\(|\))"
        tokens = re.findall(pattern, expression)
        return tokens

    @staticmethod
    def _evaluate_infix(expression):
        """
        计算中缀表达式
        :param expression: 表达式字符串
        :return: 计算结果(Fraction)
        """
        tokens = ExpressionEvaluator._tokenize(expression)
        values = []
        operators = []
        
        precedence = {'+': 1, '-': 1, '*': 2, '/': 2}
        
        for token in tokens:
            if ExpressionEvaluator._is_number(token):
                # 解析数字或分数
                num = ExpressionEvaluator._parse_number(token)
                values.append(num)
            elif token == '(':
                operators.append(token)
            elif token == ')':
                # 计算括号内的表达式
                while operators and operators[-1] != '(':
                    op = operators.pop()
                    val2 = values.pop()
                    val1 = values.pop()
                    values.append(ExpressionEvaluator._apply_operator(op, val1, val2))
                operators.pop()  # 移除 '('
            elif token in precedence:
                # 处理运算符
                while (operators and operators[-1] != '(' and
                       operators[-1] in precedence and
                       precedence[operators[-1]] >= precedence[token]):
                    op = operators.pop()
                    val2 = values.pop()
                    val1 = values.pop()
                    values.append(ExpressionEvaluator._apply_operator(op, val1, val2))
                operators.append(token)
        
        # 处理剩余的运算符
        while operators:
            op = operators.pop()
            val2 = values.pop()
            val1 = values.pop()
            values.append(ExpressionEvaluator._apply_operator(op, val1, val2))
        
        return values[0] if values else Fraction(0)

    @staticmethod
    def _is_number(token):
        """
        判断标记是否为数字
        :param token: 标记
        :return: 是否为数字
        """
        return bool(re.match(r"^\d+'?\d*/\d+$|^\d+$", token))

    @staticmethod
    def _parse_number(token):
        """
        解析数字或分数字符串
        :param token: 数字字符串
        :return: Fraction对象
        """
        if "'" in token:
            # 带分数
            parts = token.split("'")
            whole = int(parts[0])
            frac_part = parts[1].split("/")
            numerator = int(frac_part[0])
            denominator = int(frac_part[1])
            
            sign = 1 if whole >= 0 else -1
            return Fraction(sign * (abs(whole) * denominator + numerator), denominator)
        elif "/" in token:
            # 分数
            parts = token.split("/")
            numerator = int(parts[0])
            denominator = int(parts[1])
            return Fraction(numerator, denominator)
        else:
            # 整数
            return Fraction(int(token))

    @staticmethod
    def _apply_operator(operator, val1, val2):
        """
        应用运算符
        :param operator: 运算符
        :param val1: 第一个操作数
        :param val2: 第二个操作数
        :return: 运算结果
        """
        if operator == '+':
            return val1 + val2
        elif operator == '-':
            return val1 - val2
        elif operator == '*':
            return val1 * val2
        elif operator == '/':
            if val2 == 0:
                raise ZeroDivisionError("除数不能为零")
            return val1 / val2
        else:
            raise ValueError(f"不支持的运算符: {operator}")

    @staticmethod
    def format_result(result):
        """
        格式化结果为题目要求的形式
        :param result: 计算结果(Fraction)
        :return: 格式化后的字符串
        """
        if result.denominator == 1:
            return str(result.numerator)
        else:
            whole = result.numerator // result.denominator
            numerator = result.numerator % result.denominator
            
            if whole == 0:
                return f"{numerator}/{result.denominator}"
            else:
                return f"{whole}'{numerator}/{result.denominator}"

3.fraction.py - 分数处理核心

主要功能: 提供完整的分数表示、运算和规范化系统,支持自然数、真分数、带分数

python 复制代码
import math
from fractions import Fraction
import random


class MyFraction:
    """
    自定义分数类,支持真分数和带分数的表示与运算
    """

    def __init__(self, numerator=0, denominator=1, whole_number=0):
        """
        初始化分数
        :param numerator: 分子
        :param denominator: 分母
        :param whole_number: 整数部分
        """
        if denominator == 0:
            raise ValueError("分母不能为零")

        self.whole_number = whole_number
        self.numerator = numerator
        self.denominator = denominator
        self._normalize()

    @classmethod
    def from_fraction(cls, frac):
        """
        从标准Fraction对象创建MyFraction对象
        :param frac: Fraction对象
        :return: MyFraction对象
        """
        return cls(frac.numerator, frac.denominator)

    def _normalize(self):
        """
        规范化分数表示形式
        """
        # 处理负数情况
        if self.denominator < 0:
            self.numerator = -self.numerator
            self.denominator = -self.denominator

        # 化简分数
        gcd = math.gcd(abs(self.numerator), abs(self.denominator))
        self.numerator //= gcd
        self.denominator //= gcd

        # 转换假分数为带分数
        if abs(self.numerator) >= self.denominator:
            self.whole_number += (abs(self.numerator) // self.denominator) * (1 if self.numerator >= 0 else -1)
            self.numerator = abs(self.numerator) % self.denominator

    def to_improper_fraction(self):
        """
        转换为假分数形式
        :return: Fraction对象
        """
        sign = 1 if self.whole_number >= 0 or self.whole_number == 0 and self.numerator >= 0 else -1
        numerator = abs(self.whole_number) * self.denominator + abs(self.numerator)
        return Fraction(sign * numerator, self.denominator)

    def __str__(self):
        """
        字符串表示
        :return: 分数的字符串形式
        """
        if self.numerator == 0:
            return str(self.whole_number)
        elif self.whole_number == 0:
            return f"{self.numerator}/{self.denominator}"
        else:
            return f"{self.whole_number}'{self.numerator}/{self.denominator}"

    @classmethod
    def parse(cls, s):
        """
        解析字符串为分数对象
        :param s: 分数字符串,如 "3/5" 或 "2'3/8"
        :return: MyFraction对象
        """
        if "'" in s:
            # 带分数
            parts = s.split("'")
            whole_number = int(parts[0])
            frac_part = parts[1].split("/")
            numerator = int(frac_part[0])
            denominator = int(frac_part[1])
            return cls(numerator, denominator, whole_number)
        elif "/" in s:
            # 真分数
            parts = s.split("/")
            numerator = int(parts[0])
            denominator = int(parts[1])
            return cls(numerator, denominator)
        else:
            # 整数
            return cls(whole_number=int(s))

    def __add__(self, other):
        """加法"""
        result_frac = self.to_improper_fraction() + other.to_improper_fraction()
        return MyFraction.from_fraction(result_frac)

    def __sub__(self, other):
        """减法"""
        result_frac = self.to_improper_fraction() - other.to_improper_fraction()
        return MyFraction.from_fraction(result_frac)

    def __mul__(self, other):
        """乘法"""
        result_frac = self.to_improper_fraction() * other.to_improper_fraction()
        return MyFraction.from_fraction(result_frac)

    def __truediv__(self, other):
        """除法"""
        if other.to_improper_fraction() == 0:
            raise ZeroDivisionError("除数不能为零")
        result_frac = self.to_improper_fraction() / other.to_improper_fraction()
        return MyFraction.from_fraction(result_frac)

    def __eq__(self, other):
        """相等比较"""
        return self.to_improper_fraction() == other.to_improper_fraction()

    def __lt__(self, other):
        """小于比较"""
        return self.to_improper_fraction() < other.to_improper_fraction()

    def __le__(self, other):
        """小于等于比较"""
        return self.to_improper_fraction() <= other.to_improper_fraction()

    def __gt__(self, other):
        """大于比较"""
        return self.to_improper_fraction() > other.to_improper_fraction()

    def __ge__(self, other):
        """大于等于比较"""
        return self.to_improper_fraction() >= other.to_improper_fraction()


def generate_random_fraction(max_range):
    """
    生成指定范围内的随机分数
    :param max_range: 数值范围
    :return: MyFraction对象
    """
    # 以一定概率生成整数或分数
    if random.random() < 0.4:
        # 生成整数
        return MyFraction(whole_number=random.randint(0, max_range - 1))
    else:
        # 生成分数
        denominator = random.randint(2, max_range - 1)
        numerator = random.randint(1, denominator - 1)
        # 以一定概率生成带分数
        if random.random() < 0.3 and max_range > 2:
            whole_number = random.randint(0, max_range - 2)
            return MyFraction(numerator, denominator, whole_number)
        else:
            return MyFraction(numerator, denominator)

六、代码测试说明

1.测试架构说明


测试类型 测试文件 主要职责
单元测试 test_core_functions.py 测试各个核心类的独立功能
集成测试 test_integration.py 测试模块间协作和完整工作流程
异常测试 test_exceptions.py 测试边界条件和错误处理
专项测试 test_advanced_duplication.py 测试特定的去重算法

2.测试实例举例

1. 交换律重复检测测试

文件 : test_advanced_duplication.py & duplicate_test.py

python 复制代码
def test_commutative_law_detection():
    """测试交换律重复检测:2+3 和 3+2 应视为重复"""
    generator = ExpressionGenerator(10)
    expr1 = "2 + 3"
    expr2 = "3 + 2"
    canonical1 = generator._get_canonical_form(expr1)
    canonical2 = generator._get_canonical_form(expr2)
    assert canonical1 == canonical2, "交换律检测失败"

2. 结合律不重复测试

文件 : duplicate_test.py

python 复制代码
def test_associative_law_not_duplicate():
    """测试结合律不视为重复:1+2+3 和 3+2+1 不应视为重复"""
    generator = ExpressionGenerator(10)
    expr1 = "1 + 2 + 3"
    expr2 = "3 + 2 + 1"
    canonical1 = generator._get_canonical_form(expr1)
    canonical2 = generator._get_canonical_form(expr2)
    assert canonical1 != canonical2, "结合律错误地视为重复"

3. 表达式生成与验证测试

文件 : test_core_functions.py

python 复制代码
def test_expression_generation_and_validation():
    """测试表达式生成并验证可计算性"""
    generator = ExpressionGenerator(10)
    expression, answer = generator.generate_expression()
    
    # 验证格式
    assert "=" in expression
    assert len(expression) > 0
    assert len(answer) > 0
    
    # 验证可计算性
    expr_without_equal = expression.replace("=", "").strip()
    result = ExpressionEvaluator.parse_and_evaluate(expr_without_equal)
    formatted_result = ExpressionEvaluator.format_result(result)
    assert formatted_result == answer

4. 分数运算正确性测试

文件 : test_core_functions.py

python 复制代码
def test_fraction_operations():
    """测试分数运算:1/2 + 1/3 = 5/6"""
    frac1 = MyFraction(1, 2)  # 1/2
    frac2 = MyFraction(1, 3)  # 1/3
    result = frac1.to_improper_fraction() + frac2.to_improper_fraction()
    formatted = ExpressionEvaluator.format_result(result)
    assert formatted == "5/6", f"分数加法错误: 得到 {formatted}, 期望 5/6"

5. 完整工作流程测试

文件 : test_integration.py

python 复制代码
def test_complete_workflow_with_correct_answers():
    """测试完整工作流程:生成题目 → 批改正确答案 → 验证全对"""
    # 生成10道题目
    generate_exercises(10, 10)
    
    # 验证文件生成
    assert os.path.exists("Exercises.txt")
    assert os.path.exists("Answers.txt")
    
    # 批改答案
    check_answers("Exercises.txt", "Answers.txt")
    
    # 验证成绩
    with open("Grade.txt", 'r', encoding='utf-8') as f:
        grade_content = f.read()
    assert "Correct: 10" in grade_content
    assert "Wrong: 0" in grade_content

6. 错误答案批改测试

文件 : test_integration.py

python 复制代码
def test_workflow_with_wrong_answers():
    """测试错误答案批改:所有答案错误的情况"""
    generate_exercises(5, 10)
    
    # 创建错误答案文件
    wrong_answers = ["1. 999", "2. 888", "3. 777", "4. 666", "5. 555"]
    with open("WrongAnswers.txt", 'w', encoding='utf-8') as f:
        for i, answer in enumerate(wrong_answers, 1):
            f.write(f"{i}. {answer}\n")
    
    # 批改错误答案
    check_answers("Exercises.txt", "WrongAnswers.txt")
    
    # 验证成绩
    with open("Grade.txt", 'r', encoding='utf-8') as f:
        grade_content = f.read()
    assert "Correct: 0" in grade_content
    assert "Wrong: 5" in grade_content

7. 异常处理测试 - 除零错误

文件 : test_exceptions.py

python 复制代码
def test_division_by_zero_exception():
    """测试除零异常处理"""
    with pytest.raises(Exception):
        ExpressionEvaluator.parse_and_evaluate("1 / 0")

8. 异常处理测试 - 无效参数

文件 : test_exceptions.py

python 复制代码
def test_invalid_parameters():
    """测试无效参数异常"""
    with pytest.raises(SystemExit):
        generate_exercises(-1, 10)  # 负数题目数量
    
    with pytest.raises(SystemExit):
        generate_exercises(10, 0)   # 零数值范围

9. 文件不匹配异常测试

文件 : test_exceptions.py

python 复制代码
def test_file_mismatch_exception():
    """测试题目和答案文件数量不匹配异常"""
    # 创建不匹配的文件
    exercises = ["1. 1 + 1 =", "2. 2 + 2 ="]
    answers = ["1. 2"]  # 只有一个答案
    
    with open("Exercises.txt", 'w', encoding='utf-8') as f:
        f.write('\n'.join(exercises))
    with open("Answers.txt", 'w', encoding='utf-8') as f:
        f.write('\n'.join(answers))
    
    # 应该抛出异常
    with pytest.raises(Exception):
        AnswerChecker.check_answers("Exercises.txt", "Answers.txt")

10. 大规模生成性能测试

文件 : test_core_functions.py

python 复制代码
def test_large_scale_generation_performance():
    """测试大规模题目生成的性能和正确性"""
    generator = ExpressionGenerator(10)
    
    # 生成100道题目
    expressions = []
    for i in range(100):
        expr, ans = generator.generate_expression()
        expressions.append(expr)
        
        # 验证每道题都可计算
        expr_without_equal = expr.replace("=", "").strip()
        try:
            result = ExpressionEvaluator.parse_and_evaluate(expr_without_equal)
            assert result is not None
        except Exception as e:
            pytest.fail(f"表达式计算失败: {expr}, 错误: {e}")
    
    # 验证生成了100道不同的题目
    assert len(expressions) == 100
    assert len(set(expressions)) == 100, "发现重复表达式"

3.测试结果

3.1 测试高级去重检测功能

3.2 测试核心功能

3.3 测试整个工作流程

3.4 测试异常处理

七、项目小结