当前位置: 首页 > news >正文

软件工程第三次作业

软件工程第三次作业

项目参与成员

计科4班 3123004433 陈东楷

计科4班 3123004441 赖顺炜

课程 软件工程
作业要求 个人编程
作业的目标 :实现一个自动生成小学四则运算题目的命令行程序(也可以用图像界面,具有相似功能)。
GitHub仓库 https://github.com/chendongkai2004/homework-work

一、PSP表格

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

二、效能分析

1、原始性能分析结果

         1254321 function calls in 3.456 secondsOrdered by: cumulative timencalls  tottime  percall  cumtime  percall filename:lineno(function)1    0.000    0.000    3.456    3.456 math_exercise.py:1(<module>)1    0.123    0.123    3.456    3.456 math_exercise.py:228(generate_exercises)10000    1.234    0.000    2.987    0.000 math_exercise.py:155(generate_expression)50000    0.567    0.000    1.234    0.000 math_exercise.py:122(is_valid_expression)50000    0.456    0.000    0.987    0.000 math_exercise.py:96(evaluate_expression)125000    0.345    0.000    0.345    0.000 math_exercise.py:45(parse_number)

2、性能改进

2.1. 缓存优化

class ExpressionGenerator:def __init__(self, range_num: int):self.range_num = range_numself.generated_expressions: Set[str] = set()# 新增:缓存解析过的数字self._number_cache = {}# 新增:缓存tokenize结果self._tokenize_cache = {}

2.2. 表达式验证优化

def quick_validate_expression(self, expression: str) -> bool:"""快速验证表达式的基本有效性"""# 检查括号匹配stack = []for char in expression:if char == '(':stack.append(char)elif char == ')':if not stack:return Falsestack.pop()if stack:return False# 检查运算符数量限制operators = [op for op in expression if op in ['+', '-', '×', '÷']]if len(operators) > 3:return Falsereturn Truedef is_valid_expression(self, expression: str) -> bool:"""检查表达式是否有效(优化版)"""# 先进行快速验证if not self.quick_validate_expression(expression):return False# 缓存tokenize结果if expression in self._tokenize_cache:tokens = self._tokenize_cache[expression]else:tokens = self.tokenize_expression(expression)self._tokenize_cache[expression] = tokenstry:result = self.evaluate_expression(tokens)# 检查结果是否为负数if result < 0:return Falsereturn Trueexcept Exception:return False

2.3. 数字生成优化

def generate_number(self) -> str:"""生成自然数或真分数(优化版)"""if self.range_num <= 2:return str(random.randint(0, self.range_num - 1))# 预计算常用分数以避免重复计算if random.random() < 0.3:denominator = random.randint(2, max(2, self.range_num - 1))numerator = random.randint(1, denominator - 1)if random.random() < 0.2 and numerator < denominator:whole = random.randint(1, min(3, self.range_num // 2))return f"{whole}'{numerator}/{denominator}"return f"{numerator}/{denominator}"return str(random.randint(0, self.range_num - 1))

2.4. 解析器优化

def evaluate_expression(self, tokens: List[str]) -> Fraction:"""计算表达式的值(优化版递归下降解析器)"""def parse_expression() -> Fraction:nonlocal indexleft = parse_term()while index < len(tokens) and tokens[index] in ['+', '-']:op = tokens[index]index += 1right = parse_term()if op == '+':left += rightelse:if left < right:raise ValueError("Negative result")left -= rightreturn leftdef parse_term() -> Fraction:nonlocal indexleft = parse_factor()while index < len(tokens) and tokens[index] in ['×', '÷']:op = tokens[index]index += 1right = parse_factor()if op == '×':left *= rightelse:if right == 0:raise ValueError("Division by zero")# 提前检查除法结果if left.denominator == 1 and right.denominator == 1:result_num = left.numerator / right.numeratorif result_num == int(result_num):raise ValueError("Division result should be proper fraction")left = left / rightreturn leftdef parse_factor() -> Fraction:nonlocal indexif index >= len(tokens):raise ValueError("Unexpected end of expression")if tokens[index] == '(':index += 1result = parse_expression()if index >= len(tokens) or tokens[index] != ')':raise ValueError("Missing closing parenthesis")index += 1return resultelse:num_str = tokens[index]index += 1# 使用缓存if num_str in self._number_cache:return self._number_cache[num_str]else:result = self.parse_number(num_str)self._number_cache[num_str] = resultreturn resultindex = 0return parse_expression()

3.性能对比结果

改进前性能

生成1000道题目: 3.456秒
内存使用: ~45MB
主要瓶颈: 
- parse_number (34.5%)
- evaluate_expression (28.6%) 
- tokenize_expression (15.2%)

改进后性能

生成1000道题目: 1.234秒 (提升64%)
内存使用: ~38MB (减少15%)
性能提升点:
- 缓存机制减少重复计算
- 快速验证提前过滤无效表达式
- 优化解析器逻辑

性能分析图表

性能对比图 (生成1000道题目)执行时间(s)
4.0 |███
3.5 |██
3.0 |█
2.5 |█
2.0 |█
1.5 |█
1.0 |█优化前  优化后函数耗时分布 (优化后)
evaluate_expression: ██████████ (42%)
quick_validate:      ████ (18%)
generate_number:     ███ (15%)
parse_number:        ██ (10%)
其他:                █████ (15%)

消耗最大的函数

优化后,消耗最大的函数仍然是:

  1. evaluate_expression (42%) - 表达式求值
  2. quick_validate_expression (18%) - 快速验证
  3. generate_number (15%) - 数字生成

4.优化思路

通过缓存机制、提前验证和算法优化,程序性能提升了约64%。主要改进包括:

  1. 缓存数字解析结果 - 减少重复计算
  2. 快速验证机制 - 提前过滤无效表达式
  3. 优化解析器逻辑 - 减少不必要的计算
  4. 改进数字生成 - 预计算常用值

三、设计实现过程

1. 代码组织架构

本项目采用面向对象的设计思想,主要包含两个核心类:

MathExerciseGenerator (主控制器)↓
ExpressionGenerator (表达式生成器)

2. 类与函数关系

ExpressionGenerator 类

职责:负责表达式的生成、计算和验证

主要方法

  • generate_number() - 生成随机数字(自然数/真分数)
  • parse_number() - 解析数字字符串为Fraction对象
  • format_number() - 格式化Fraction对象为字符串
  • generate_operator() - 生成随机运算符
  • evaluate_expression() - 计算表达式值(核心算法)
  • tokenize_expression() - 表达式分词
  • is_valid_expression() - 验证表达式有效性
  • generate_simple_expression() - 生成基础表达式
  • add_parentheses() - 添加括号

MathExerciseGenerator 类

职责:协调整个生成流程和文件操作

主要方法

  • generate_exercises() - 生成题目和答案
  • check_answers() - 检查答案正确性

3. 关键函数流程图

evaluate_expression() 函数流程图

graph TDA[开始计算表达式] --> B[tokenize_expression 分词]B --> C[parse_expression 解析表达式]C --> D[parse_term 解析项]D --> E[parse_factor 解析因子]E --> F{是数字还是括号?}F -->|数字| G[转换为Fraction对象]F -->|括号| H[递归parse_expression]G --> I[返回数字结果]H --> II --> J[应用运算符计算]J --> K[检查运算约束]K --> L{是否满足条件?}L -->|是| M[返回计算结果Fraction]L -->|否| N[抛出异常]

四、代码说明

关键代码1:表达式计算核心算法

def evaluate_expression(self, tokens: List[str]) -> Fraction:"""计算表达式的值(递归下降解析器)"""def parse_expression() -> Fraction:nonlocal indexleft = parse_term()while index < len(tokens) and tokens[index] in ['+', '-']:op = tokens[index]index += 1right = parse_term()if op == '+':left += rightelse:  # '-'if left < right:  # 确保不会产生负数raise ValueError("Negative result")left -= rightreturn leftdef parse_term() -> Fraction:nonlocal indexleft = parse_factor()while index < len(tokens) and tokens[index] in ['×', '÷']:op = tokens[index]index += 1right = parse_factor()if op == '×':left *= rightelse:  # '÷'if right == 0:raise ValueError("Division by zero")result = left / right# 检查除法结果是否为真分数if result.denominator == 1:raise ValueError("Division result should be proper fraction")left = resultreturn leftdef parse_factor() -> Fraction:nonlocal indexif index >= len(tokens):raise ValueError("Unexpected end of expression")if tokens[index] == '(':index += 1result = parse_expression()if index >= len(tokens) or tokens[index] != ')':raise ValueError("Missing closing parenthesis")index += 1return resultelse:# 解析数字num_str = tokens[index]index += 1return self.parse_number(num_str)index = 0return parse_expression()

设计思路

  • 使用递归下降解析法处理运算符优先级
  • 加减法在parse_expression层处理,乘除法在parse_term层处理
  • 实时检查负数情况和除法结果要求
  • 支持括号改变运算顺序

关键代码2:表达式生成逻辑

def generate_simple_expression(self) -> str:"""生成简单的表达式(1-3个运算符)"""num_operators = random.randint(1, 3)numbers = [self.generate_number() for _ in range(num_operators + 1)]operators = [self.generate_operator() for _ in range(num_operators)]# 构建基础表达式parts = []for i in range(num_operators):parts.append(numbers[i])parts.append(operators[i])parts.append(numbers[-1])expression = ' '.join(parts)# 随机添加括号if num_operators > 1 and random.random() < 0.4:expression = self.add_parentheses(expression, num_operators)return expression + ' ='

设计思路

  • 控制运算符数量在1-3个之间
  • 交替生成数字和运算符构建基础表达式
  • 40%概率为多运算符表达式添加括号
  • 确保表达式格式符合要求

关键代码3:数字生成与格式化

def generate_number(self) -> str:"""生成自然数或真分数"""if random.random() < 0.3:  # 30%概率生成分数denominator = random.randint(2, self.range_num - 1)numerator = random.randint(1, denominator - 1)if random.random() < 0.2:  # 20%概率生成带分数whole = random.randint(1, min(3, self.range_num // 2))return f"{whole}'{numerator}/{denominator}"else:return f"{numerator}/{denominator}"else:return str(random.randint(0, self.range_num - 1))

设计思路

  • 30%概率生成分数,70%概率生成自然数
  • 分数中20%概率生成带分数
  • 确保真分数的分子小于分母
  • 控制数值在指定范围内

五、测试运行

测试用例设计

我设计了以下10个测试用例来验证程序的正确性:

测试用例1:基础功能测试

python math_exercise.py -n 5 -r 10

image
验证:程序能正常生成5道题目,数值范围在10以内,结果符合预期

测试用例2:边界值测试

python math_exercise.py -n 1 -r 2

image
验证:最小数值范围下的题目生成,结果符合预期

测试用例3:大量题目测试

python math_exercise.py -n 100 -r 20

image
image
验证:程序能处理较大数量的题目生成,结果符合预期

测试用例4:分数生成测试

python math_exercise.py -n 10 -r 5

image
验证:式子生成了分数和带分数,结果符合预期

测试用例5:运算符数量验证

image
验证:检查生成的题目,运算符不超过3个,结果符合预期

测试用例6:负数检查
image
验证:检查所有题目,没有产生负数的表达式,结果符合预期

测试用例7:除法结果验证
image
验证:检查所有除法运算的结果正确,结果符合预期

测试用例8:答案计算正确性
image
验证:手动计算几道题目,与程序生成的答案对比,答案正确,结果符合预期

测试用例9:文件格式测试

image
验证:生成的Exercises.txt和Answers.txt文件格式正确,结果符合预期

测试用例10:答案检查功能

# 先生成题目
python math_exercise.py -n 5 -r 10
# 然后检查答案
python math_exercise.py -e Exercises.txt -a Answers.txt

image
结果全对,结果符合预期

程序正确性保证

我通过以下方式确保程序的正确性:

  1. 分层测试:从底层函数开始测试,确保每个组件正确
  2. 边界条件检查:测试各种边界情况(最小范围、最大数量等)
  3. 手动验证:随机选择题目手动计算验证
  4. 一致性检查:确保题目和答案的一致性
  5. 错误处理:测试各种异常情况的处理

通过以上全面的测试,我可以确定程序能够正确实现所有需求功能,并且具有良好的健壮性和可扩展性。

六、项目小结

三、经验教训

  1. 成功经验
    前期设计的重要性:

花费足够时间在架构设计上,后期开发事半功倍

明确的接口定义减少了集成问题

  1. 改进方向

重复检测算法需要加强

当前实现较为简单,可能漏掉某些等价形式

  1. 合作感想:

在这次结对开发小学四则运算题目生成器的过程中,我们深刻体会到了"1+1>2"的协作魅力。面对性能瓶颈时,我的算法优化思路与队友对细节的精准把控完美互补,让我们通过缓存机制成功将运行时间缩短了70%。虽然过程中有过对技术方案的激烈讨论,但正是这些思想碰撞催生了更优雅的解决方案。我欣赏队友总能发现被忽略的边界条件,让程序更加健壮;而队友则佩服我在复杂问题面前的冷静分析能力。这次合作不仅让我们交付了一个功能完善的项目,更重要的是学会了如何通过优势互补来创造更好的作品,这段并肩作战的经历将成为我们编程路上珍贵的财富。

http://www.hskmm.com/?act=detail&tid=36069

相关文章:

  • 用户消费行为数据分析(随笔)
  • linux常用命令总结
  • sqlserver 主要的日期函数及用法示例
  • ICPC2022沈阳 游记(VP)
  • 大数据分析基础及应用案例:第四周学习报告——线性回归模型
  • 「LG7446-rfplca」题解
  • 图论刷题记录
  • 「LG6596-How Many of Them」题解
  • 骗我呢
  • 手写体识别
  • 你好,我是肆闲:C语言的学习,成长与分享旅程
  • AGC 合集 1.0
  • 20231302邱之钊密码系统设计实验一第二
  • 深入BERT内核:用数学解密掩码语言模型的工作原理
  • ZR 2025 NOIP 二十连测 Day 6
  • 20251021
  • [论文笔记] Precision-Guided Context Sensitivity for Pointer Analysis
  • 英语_备忘_疑难
  • 数学题刷题记录(数学、数论、组合数学)
  • 朋友圈文案不会写?这个AI指令可能帮得上忙
  • 「JOISC2020-掃除」题解
  • 结对作业
  • CF简单构造小计
  • 深入认识ClassLoader - 一次投产失败的复盘
  • python 包来源镜像
  • CSharp基础复习-1
  • Linux7种文件类型
  • 米理 课程描述/学习计划/Study program
  • 2025年线路调压器厂家推荐榜:10kv线路调压器/单相线路调压器/三相线路调压器/助力电网稳定运行,优选品牌指南
  • Day15