一、作业基本信息
| 项目 | 内容 |
|---|---|
| 这个作业属于哪个课程 | 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_exercises → generate_expression → _evaluate_expression_string → parse_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_expressionN 次(题目数量) - 每个表达式都可能经历多次尝试(最多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, "发现重复表达式"