结对项目
算术题目生成器
这个作业属于哪个课程 | <计科22级34班> |
---|---|
这个作业要求在哪里 | <作业要求> |
这个作业的目标 | 培养学生的综合运用编程技能、问题解决能力和团队协作能力 |
团队成员 | 学号 |
---|---|
吴秋雪 | 3222004892 |
何晓漫 | 3222004765 |
项目 GitHub 链接:https://github.com/Tracywu1/FourOperationsProblemGenerator
🧩一、PSP表格
PSP各个阶段 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 50 | 50 |
· Estimate | · 明确需求和其他因素,估计以下的各个任务需要多少时间 | 50 | 50 |
Development | 开发 | 390 | 435 |
· Analysis | · 需求分析 (包括学习新技术、新工具的时间) | 60 | 50 |
· Design Spec | · 生成设计文档(整体框架的设计,各模块的接口,用时序图,快速原型等方法) | 60 | 60 |
· Design Review | · 设计复审 | 10 | 5 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 10 |
· Design | · 具体设计 | 30 | 20 |
· Coding | · 具体编码 | 120 | 200 |
· Code Review | · 代码复审 | 30 | 30 |
· Test | · 测试(自我测试,修改代码,提交修改) | 60 | 60 |
Reporting | 报告 | 160 | 180 |
· Test Report | · 测试报告(发现了多少bug,修复了多少) | 30 | 50 |
· Size Measurement | · 计算工作量(多少行代码,多少次签入,多少测试用例,其他工作量) | 10 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划(包括写文档、博客的时间) | 120 | 120 |
· 合计 | 570 | 665 |
🧩二、效能分析
下图是用 PyCharm 内置的效能分析工具生成的:
1.效能分析:
- 主要瓶颈:
main
函数耗时最长,占用了总运行时间的99.6%(18340 ms)。这是程序的核心入口点,大部分时间都花在这里。
- 重要子函数:
mainloop
函数占用了总时间的97.9%(18021 ms),是main
函数中最耗时的部分。这可能是因为它包含了主要的事件循环。<method 'mainloop' of '_tkinter.tkapp' objects>
占用了70.9%的时间,这是 Tkinter GUI 的主循环,说明大部分时间都花在了等待和处理用户界面事件上。
- GUI 相关操作:
__init__
方法(可能是 GUI 初始化)占用了较少的时间,说明 GUI 的初始化不是性能瓶颈。
2.改进思路:
- 优化主事件循环:由于大部分时间都花在了
mainloop
和 Tkinter 的事件处理上,可以考虑优化 GUI 更新频率或使用更轻量级的 GUI 框架。 - 异步处理:考虑将一些耗时的操作(如文件 I/O 或复杂计算)放在后台线程中执行,以提高 GUI 的响应性。
- 缓存优化:如果有重复的计算或数据获取,可以考虑使用缓存来减少重复操作。
- 代码优化:虽然生成表达式的函数不是主要瓶颈,但仍可以考虑优化算法,特别是如果需要生成大量表达式时。
- 性能分析:对
mainloop
内部进行更细致的性能分析,找出可能的优化点。
总的来说,这个项目的主要性能瓶颈在于 GUI 的事件循环,而不是算术表达式的生成或计算部分。优化应该主要集中在提高 GUI 的响应性和效率上。但是由于总花费时间只有 18340 ms,并且我们在使用过程中并未觉得 GUI 的响应慢以及效率低,故我们认为该项目没有改进的必要性,项目效能已经达到较高标准。
🧩三、 设计实现过程
1.类结构设计:
Expression
:表示一个基本的表达式Operation
:继承自Expression
,表示一个运算操作ExpressionGenerator
:负责生成单个表达式ArithmeticProblemGenerator
:负责生成整套算术题目ArithmeticGUI
:提供图形用户界面
2.类之间的关系如下:
Operation
是Expression
的子类ExpressionGenerator
使用Expression
和Operation
来生成表达式ArithmeticProblemGenerator
使用ExpressionGenerator
来生成多个题目ArithmeticGUI
使用ArithmeticProblemGenerator
来生成题目并提供用户界面
3.关键函数流程图:
这个流程图展示了 ArithmeticProblemGenerator
类中 generate_problems
方法的主要逻辑。
🧩四、代码说明
1.Expression 类
class Expression:
def __init__(self, value):
self.value = value
self.is_operation = False
self.operator_count = 0
def evaluate(self):
return self.value
def to_string(self):
return str(self)
Expression类是最基本的表达式单元,可以是一个数值或者一个复杂的表达式。
2.Operation 类
class Operation(Expression):
def __init__(self, left, right, operator):
super().__init__(None)
self.left = left
self.right = right
self.operator = operator
self.is_operation = True
self.operator_count = left.operator_count + right.operator_count + 1
if not self.is_valid_operation():
raise ValueError("Invalid operation")
def is_valid_operation(self):
# 检查运算的有效性
# ...(省略具体实现)
def evaluate(self):
# 根据运算符计算结果
# ...(省略具体实现)
Operation类继承自Expression,表示一个具体的运算操作。它包含左右两个操作数和一个运算符。
3.ExpressionGenerator 类
class ExpressionGenerator:
def __init__(self, max_value):
self.max_value = max_value
def generate_number(self):
# 生成随机数或分数
# ...(省略具体实现)
def generate_expression(self, max_operators=3):
# 递归生成表达式
# ...(省略具体实现)
ExpressionGenerator类负责生成随机的表达式,可以是单个数字、分数或复杂的四则运算表达式。
4.ArithmeticProblemGenerator 类
class ArithmeticProblemGenerator:
def __init__(self, num_problems, max_value):
self.num_problems = num_problems
self.max_value = max_value
self.expression_generator = ExpressionGenerator(max_value)
self.problems = set()
def generate_problems(self):
# 生成指定数量的不重复题目
# ...(省略具体实现)
def save_problems(self):
# 将生成的题目保存到文件
# ...(省略具体实现)
def save_answers(self):
# 计算并保存答案到文件
# ...(省略具体实现)
ArithmeticProblemGenerator类是整个程序的核心,负责生成完整的算术题目集,并将题目和答案保存到文件中。
🧩五、测试运行
为了确保程序的正确性,我们进行了以下测试:
1.测试简单表达式生成:
expr = Expression(5)
assert str(expr) == "5"
assert expr.evaluate() == 5
2.测试有效运算:
op = Operation(Expression(3), Expression(2), '+')
assert str(op) == "(3 + 2)"
assert op.evaluate() == 5
3.测试无效运算(除以零):
with pytest.raises(ValueError):
Operation(Expression(3), Expression(0), '÷')
4.测试复杂表达式:
left = Operation(Expression(3), Expression(2), '+')
right = Expression(4)
op = Operation(left, right, '×')
assert str(op) == "((3 + 2) × 4)"
assert op.evaluate() == 20
5.测试随机数生成范围:
generator = ExpressionGenerator(10)
for _ in range(100):
num = generator.generate_number()
assert isinstance(num, (int, Fraction))
if isinstance(num, int):
assert 0 <= num < 10
else:
assert 0 < num.numerator <= 10
assert 0 < num.denominator <= 10
6.测试表达式生成复杂度:
generator = ExpressionGenerator(10)
for _ in range(100):
expr = generator.generate_expression()
assert expr.operator_count <= 3
7.测试问题生成数量:
generator = ArithmeticProblemGenerator(10, 10)
generator.generate_problems()
assert len(generator.problems) == 10
8.测试问题唯一性:
generator = ArithmeticProblemGenerator(100, 10)
generator.generate_problems()
assert len(generator.problems) == len(set(generator.problems))
9.测试表达式计算:
generator = ArithmeticProblemGenerator(1, 10)
assert generator.evaluate_expression("2 + 3") == 5
assert generator.evaluate_expression("6 - 4") == 2
assert generator.evaluate_expression("3 × 4") == 12
assert generator.evaluate_expression("8 ÷ 2") == 4
10.测试结果格式化:
generator = ArithmeticProblemGenerator(1, 10)
assert generator.format_result(5) == "5"
assert generator.format_result(Fraction(1, 2)) == "1/2"
assert generator.format_result(Fraction(5, 2)) == "2'1/2"
assert generator.format_result(3.5) == "3.5"
通过这些测试,我们可以确保程序的各个组件都能正确工作,包括表达式生成、运算执行、问题生成、结果计算和格式化等关键功能。这些测试覆盖了各种可能的情况,包括边界条件和特殊情况,从而增强了我们对程序正确性的信心。
🧩六、 项目小结
1.成功之处
- 模块化设计:我们成功地将程序分解为多个独立的类,每个类负责特定的功能,这使得代码易于理解和维护。
- 递归算法:使用递归方法生成表达式是一个巧妙的设计,它使得生成复杂表达式变得简单。
- 全面的测试:我们编写了详细的单元测试,覆盖了主要功能和边界情况,这大大提高了程序的可靠性。
2.需要改进的地方
- 性能优化:在生成大量题目时,程序可能会变慢。我们可以考虑使用缓存或其他优化技术来提高性能。
- 用户界面:虽然我们实现了基本的GUI,但它可以进一步改进,使其更加用户友好。
- 题目难度控制:目前,题目的难度主要由最大值和操作符数量控制。我们可以考虑添加更细致的难度控制机制。
3.结对感受
结对编程是一次很好的学习经历。通过实时讨论和代码审查,我们能够及时发现和纠正错误,同时也学习到了彼此的编程技巧和思维方式。
4.闪光点和建议
- 漫:雪在设计类结构时的思路很清晰,这帮助我们快速搭建了程序的框架。建议在今后的项目中,可以更多地运用设计模式来优化代码结构。
- 雪:漫在编写单元测试时很细心,考虑了许多边界情况,这大大提高了程序的健壮性。建议在编写主要功能的同时就开始编写测试,这样可以更早地发现和解决问题。
总的来说,这个项目让我们深入理解了面向对象编程和测试驱动开发的重要性,也让我们体会到了结对编程的价值。我们期待在未来的项目中能够继续改进和应用这些经验。