这个作业属于哪个课程 | 软件工程2班 |
---|---|
这个作业要求在哪里 | 结对项目 |
这个作业的目标 | 团队共同实现一个自动生成小学四则运算题目的命令行程序 |
合作人员
- 朱雅子 3223004823
- 黄海怡 3223004296
项目地址Github链接
一、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 30 |
· Estimate | 估计这个任务需要多少时间 | 10 | 10 |
Development | 开发 | 240 | 180 |
· Analysis | 需求分析 (包括学习新技术) | 40 | 30 |
· Design Spec | 生成设计文档 | 30 | 10 |
· Design Review | 设计复审 | 30 | 40 |
· Coding Standard | 代码规范 (制定开发规范) | 10 | 10 |
· Design | 具体设计 | 30 | 20 |
· Coding | 具体编码 | 120 | 90 |
· Code Review | 代码复审 | 30 | 20 |
· Test | 测试(自我测试,修改代码,提交修改) | 60 | 60 |
Reporting | 报告 | 60 | 30 |
· Test Report | 测试报告 | 30 | 30 |
· Size Measurement | 计算工作量 | 10 | 10 |
· Postmortem | 事后总结, 并提出过程改进计划 | 20 | 20 |
Total | 合计 | 670 | 600 |
二、项目简介
实现功能
开发一个具有图形界面的四则运算题目生成工具,支持自定义题目数量、数值范围和真分数选项,能够自动生成题目文件(Exercises.txt)和答案文件(Answers.txt),并提供答案批改功能(生成Grade.txt)。
使用方法
点击main函数运行,弹出窗口。点击是否要真分数,输入生成的式子数量和范围。点击生成按钮,就可以在Exercises.txt看见式子,在Answers.txt看见答案。(注:如果弹出提示范围不够大需要重新输入范围)生成后会弹出窗口提示。可以点击批改按钮选择要批改的txt文件。(测试,可以把Answers.txt的内容复制粘贴到要批改的答案文档,修改要测试的答案号码来测试批改功能)批改完成后会弹出提示窗口,批改结果可以在Grade.txt中查看。
项目结构
MathGeneratorGUI
├── init() # 初始化GUI窗口
├── create_widgets() # 创建界面组件(输入框、按钮等)
├── generate_expression() # 递归生成表达式树
├── normalize_expression()# 规范化表达式(排序操作数)
├── generate_number() # 生成数字(自然数或真分数)
├── calculate() # 计算表达式值
├── validate_input() # 验证用户输入合法性
├── generate() # 主生成流程(写入文件)
├── grade_answers() # 批改答案功能
└── 辅助方法
├── _format_answer() # 格式化答案输出
├── _parse_answer() # 解析答案字符串
└── _read_answer_file()# 读取答案文件
三、效能分析
性能的改进
思路
考虑到以下优点,将运算式子的生成模式改进为采用二叉树的递归算法,从有想法到实现耗时一小时。
- 二叉树具有天然的层级优势,可控制剩余运算符数量
- 将随机数与随机运算符分成两部分
效果
分析:根据代码分析,耗时最大的函数是generate_expression及其调用的normalize_expression。
改进方法
- 优化除法条件判断
问题:允许分数时,除法要求结果不能为整数,导致多次重试
改进:允许除法结果为整数,简化条件判断 - 优化查重数据结构
问题:字符串集合查重效率低
改进:使用数值哈希(如表达式结果)辅助查重
四、代码设计
函数设计
流程图
程序主流程
核心函数流程
1、generate_expression
2、normalize_expression
3、Generate
五、核心代码
generate_expression递归生成四则运算表达式,确保数学合法性并避免重复。
def generate_expression(self, remaining_ops, range_limit, allow_fraction, generated):MAX_RETRIES = 100for _ in range(MAX_RETRIES):try:if remaining_ops == 0:# 生成原子表达式(单个数字)# 递归终止条件(最低一层,都是数字)num = self.generate_number(range_limit, allow_fraction)expr = str(num)if expr in generated:continuegenerated.add(expr)return expr, numop = random.choice(['+', '-', '×', '÷'])left_ops = random.randint(0, remaining_ops - 1)right_ops = remaining_ops - 1 - left_opsleft_exp, left_val = self.generate_expression(left_ops, range_limit, allow_fraction, generated)right_exp, right_val = self.generate_expression(right_ops, range_limit, allow_fraction, generated)# 生成左右子树立即规范化left_exp = self.normalize_expression(left_exp)right_exp = self.normalize_expression(right_exp)if op == '-' and left_val < right_val:left_exp, right_exp = right_exp, left_expleft_val, right_val = right_val, left_valif op == '÷':for _ in range(MAX_RETRIES):if right_val != 0 and not (allow_fraction and (left_val / right_val).denominator == 1):breakright_exp, right_val = self.generate_expression(right_ops, range_limit, allow_fraction,generated)else:continue# 按数值排序操作数if op in ['+', '×']:if left_val > right_val:left_exp, right_exp = right_exp, left_expleft_val, right_val = right_val, left_valexpr = f"{left_exp} {op} {right_exp}"else:expr = f"{left_exp} {op} {right_exp}"if remaining_ops > 1:expr = f"({expr})"# 直接返回规范化后的表达式if expr in generated:continuegenerated.add(expr)value = self.calculate(op, left_val, right_val)return expr, valueexcept (ValueError, ZeroDivisionError):continueraise RuntimeError(f"无法生成合法表达式,建议扩大数值范围(当前r={range_limit})")
normalize_expression规范化表达式结构,消除交换律导致的重复。
def normalize_expression(self, expr): #让所有表达式规范化,便于查重# 移除所有括号expr = expr.replace('(', '').replace(')', '')def sort_sub_expressions(e):# 递归拆分表达式并排序if '×' in e or '÷' in e:# 处理乘除优先parts = []current = []ops = []for c in e:if c in ['×', '÷']:parts.append(''.join(current).strip())ops.append(c)current = []else:current.append(c)parts.append(''.join(current).strip())# 对乘法项排序(数值从小到大)for i in range(len(parts)):if i < len(ops) and ops[i] == '×':parts[i] = self.normalize_expression(parts[i])parts[i + 1] = self.normalize_expression(parts[i + 1])# 按数值排序left = self._parse_number(parts[i])right = self._parse_number(parts[i + 1])if left > right:parts[i], parts[i + 1] = parts[i + 1], parts[i]# 重组表达式sorted_expr = parts[0]for i in range(len(ops)):sorted_expr += f' {ops[i]} {parts[i + 1]}'return sorted_exprelse:# 处理加法add_parts = e.split('+')# 转换为数值排序add_nums = [self._parse_number(p) for p in add_parts]sorted_parts = sorted(zip(add_nums, add_parts), key=lambda x: x[0])return '+'.join([p[1] for p in sorted_parts])return sort_sub_expressions(expr)
六、测试
以下是以测试代码形式重组的10个关键测试用例,附带正确性验证说明。所有测试基于unittest框架,需与主程序文件main.py配合使用
# 测试用例1:边界值输入验证def test_min_valid_input(self):with patch.object(self.gui, 'n_entry') as mock_n, \patch.object(self.gui, 'r_entry') as mock_r:mock_n.get.return_value = "1"mock_r.get.return_value = "10"self.assertEqual(self.gui.validate_input(), (1, 10))# 测试用例2:非法字符输入检测def test_invalid_character_input(self):with patch.object(self.gui, 'n_entry') as mock_n, \patch.object(self.gui, 'r_entry') as mock_r:mock_n.get.return_value = "5a"mock_r.get.return_value = "20"self.assertIsNone(self.gui.validate_input())# 测试用例3:带分数运算逻辑def test_mixed_number_calculation(self):result = self.gui.calculate('÷', Fraction(7, 2), Fraction(1, 2))self.assertEqual(result, Fraction(7))# 测试用例4:表达式去重验证def test_expression_normalization(self):expr1 = self.gui.normalize_expression("3+5")expr2 = self.gui.normalize_expression("5+3")self.assertEqual(expr1, expr2)# 测试用例5:除零保护测试def test_division_by_zero_protection(self):with self.assertRaises(ValueError):self.gui.calculate('÷', Fraction(3, 4), Fraction(0))# 测试用例6:文件格式验证def test_output_file_formatting(self):test_exercises = {1: "3+2",2: "1/2 × 4",3: "(5-2) ÷ 3"}with patch.object(self.gui, 'generate_exercises', return_value=test_exercises):self.gui.generate()with open("Exercises.txt", "r") as f:lines = f.readlines()self.assertTrue(lines[0].startswith("[1] "))# 测试用例7:括号优先级验证def test_bracket_priority(self):expr = "(1/2 + 3) × 4"normalized = self.gui.normalize_expression(expr)self.assertIn("×(4)", normalized) # 验证乘法符号位置# 测试用例8:答案格式容错def test_answer_format_parsing(self):with self.assertRaises(ValueError):self.gui._parse_answer("2’3/4") # 错误的中文撇号# 测试用例9:大数据量压力测试def test_large_scale_generation(self):with patch.object(self.gui, 'n_entry') as mock_n, \patch.object(self.gui, 'r_entry') as mock_r:mock_n.get.return_value = "10000"mock_r.get.return_value = "100"self.gui.generate()self.assertEqual(len(self.gui.exercises), 10000)# 测试用例10:运算符分布验证def test_operator_distribution(self):operators = set()for _ in range(100):expr = self.gui.generate_expression(10, True)ops = {c for c in expr if c in '+-×÷'}operators.update(ops)self.assertEqual(operators, {'+', '-', '×', '÷'})def tearDown(self):self.test_dir.cleanup()if __name__ == "__main__":
unittest.main(verbosity=2)
思路:
七、项目小结
项目亮点
1、数学规则严谨性(确保减法结果非负、除法分母合法)
2、高效查重机制(通过规范化表达式实现O(1)时间复杂度的重复判断)
3、用户友好性(图形界面操作简单,支持错误提示和状态反馈)
4、数据结果高效性(通过递归生成和规范化机制,本项目能够高效生成符合数学规则的题目,适用于小学数学教育场景)
通过项目积累的经验
这个项目不仅让我们巩固了二叉树、递归这样的技术方法,也让我们对一整套开发流程有了更深刻的理解。开发不仅仅是一个人的事,也是团队里的每个人都要参与讨论编写的,从程序到测试到文档,我们应当合理分工、用心完成。
结对感受
黄海怡to朱雅子:非常给力的队友,执行力强,思维缜密,代码水平高,教会了我许多!
朱雅子to黄海怡:两个人工作减少了工作量,可以专注在自己想做的领域。