软件工程第三次作业——结对项目
这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience/homework/13470 |
这个作业的目标 | 练习合作完成一个项目 |
小组成员及项目信息
姓名 | 学号 |
---|---|
徐新曜 | 3123004634 |
许国伟 | 3123004200 |
Github项目地址: math-generator-cli
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 40 |
· Estimate | · 估计这个任务需要多少时间 | 30 | 40 |
Development | 开发 | 360 | 420 |
· Analysis | · 需求分析(包括学习新技术) | 60 | 70 |
· Design Spec | · 生成设计文档 | 40 | 50 |
· Design Review | · 设计复审 | 20 | 30 |
· Coding Standard | · 代码规范(为目前的开发制定合适的规范) | 20 | 20 |
· Design | · 具体设计 | 50 | 60 |
· Coding | · 具体编码 | 120 | 150 |
· Code Review | · 代码复审 | 30 | 40 |
· Test | · 测试(自我测试,修改代码,提交修改) | 120 | 150 |
Reporting | 报告 | 60 | 80 |
· Test Repor | · 测试报告 | 40 | 50 |
· Size Measurement | · 计算工作量 | 10 | 15 |
· Postmortem & Process Improvement Plan | · 事后总结,并提出过程改进计划 | 10 | 15 |
· 合计 | 570 | 690 |
效能分析
在本次项目开发过程中,我们投入了约30分钟用于性能分析和优化。主要优化思路包括:
-
内存使用优化:在生成大量题目时,使用集合(set)来存储已生成的题目,确保题目唯一性的同时提高查找效率。
-
算法优化:
- 改进了分数生成算法,避免重复计算
- 优化了括号插入算法,减少不必要的字符串拼接操作
-
文件操作优化:使用更高效的文件写入方式,减少I/O操作次数。
经过优化后,程序在生成10000道题目的情况下,内存占用降低了约15%,生成速度提升了约20%。
-
效能图:
设计实现过程
整体架构设计
本项目采用模块化设计,将功能划分为多个独立的模块,便于维护和扩展。整体架构如下:
main.py - 程序入口点
├── argument_parser.py - 命令行参数解析
├── generator.py - 题目生成器
├── calculator.py - 答案计算器
├── validator.py - 答案校验器
├── file_handler.py - 文件处理
├── utils.py - 工具函数
└── config.py - 配置和常量定义
类和函数设计
- Main类:程序入口,协调各模块工作
- ArgumentParser类:解析和验证命令行参数
- Generator类:负责生成四则运算题目,支持整数和分数运算
- Calculator类:计算表达式结果,处理分数运算
- Validator类:校验答案正确性
- FileHandler类:处理文件读写操作
- Utils类:提供各种工具函数
关键函数流程图
以下是题目生成的核心流程:
代码说明
1. 题目生成器(Generator类)
class Generator:
def __init__(self):
"""初始化生成器"""
self.operators = ['+', '-', '*', '/']
self.questions = []
self.used_questions = set()
def generate_questions(self, count, range_limit, include_fractions=True, with_answers=False):
"""生成指定数量的题目,支持真分数"""
print(f"正在生成 {count} 道题目,数值范围:0-{range_limit},包含真分数: {include_fractions}...")
for i in range(1, count + 1):
if include_fractions and random.random() < 0.5: # 50%概率生成分数题目
question = self.generate_fraction_expression(range_limit)
else:
question = self.generate_simple_expression(range_limit)
self.questions.append((i, question))
# 输出题目到文件
from file_handler import FileHandler
output_file = "questions.txt"
if FileHandler.write_questions(output_file, self.questions):
print(f"题目已生成并保存到: {output_file}")
# 生成答案文件
self.generate_answer_template(with_answers)
这个函数是题目生成的核心,根据参数决定生成整数题目还是分数题目,并支持自动生成答案。
2. 分数表达式生成
def generate_fraction_expression(self, range_limit, include_parentheses=True):
"""生成包含真分数的表达式,支持括号"""
# 随机选择运算符数量(1-3个)
operator_count = random.randint(1, 3)
expression_parts = []
# 生成第一个操作数
if random.random() < 0.7: # 70%概率生成分数
num, den = Utils.generate_true_fraction(range_limit, allow_mixed=True)
fraction_str = Utils.format_fraction(num, den, mixed=True)
expression_parts.append(fraction_str)
else:
integer = Utils.random_number(range_limit)
expression_parts.append(str(integer))
# 生成后续的操作数和运算符
for i in range(operator_count):
# 选择运算符
operator = self._get_safe_operator(expression_parts[-1], range_limit)
expression_parts.append(operator)
# 根据运算符类型生成下一个操作数
if operator == '/':
next_operand = self._generate_safe_divisor(expression_parts[-2], range_limit)
elif operator == '-':
next_operand = self._generate_safe_subtrahend(expression_parts[-2], range_limit)
else:
# 加法和乘法可以正常生成
if random.random() < 0.7: # 70%概率生成分数
num, den = Utils.generate_true_fraction(range_limit, allow_mixed=True)
fraction_str = Utils.format_fraction(num, den, mixed=True)
next_operand = fraction_str
else:
next_operand = str(Utils.random_number(range_limit))
expression_parts.append(next_operand)
# 插入括号
if include_parentheses and operator_count >= 1:
return Utils.insert_parentheses(expression_parts)
else:
return " ".join(expression_parts)
这个函数负责生成包含真分数的表达式,支持括号,并确保生成的表达式符合数学规则(例如避免负数结果和除零错误)。
3. 计算器实现
def calculate_expression(self, expression):
"""计算表达式的值,支持真分数和带分数"""
try:
from fractions import Fraction
import re
# 将整数也转为Fraction,避免浮点精度问题
def replace_number(match):
num_str = match.group()
if '/' in num_str or "'" in num_str:
# 处理分数和带分数
fraction_obj = Utils.parse_mixed_fraction(num_str)
return f"Fraction({fraction_obj.numerator}, {fraction_obj.denominator})"
else:
# 处理整数
return f"Fraction(int({num_str}), 1)"
# 匹配所有数字(整数、分数、带分数)
expr = re.sub(r"\d+'\d+/\d+|\d+/\d+|\d+", replace_number, expression)
# 计算表达式结果
result = eval(expr, {'Fraction': Fraction})
# 确保返回Fraction对象
if isinstance(result, Fraction):
return result
else:
return Fraction(result)
except Exception as e:
Utils.print_error(f"计算表达式失败: {expression} - {str(e)}", exit_code=5)
计算器使用Python的Fraction类来处理分数运算,避免浮点精度问题,并支持解析和计算包含真分数和带分数的表达式。
4. 答案校验功能
def validate_answers(self, exercise_file, answer_file):
"""校验答案的正确性"""
print(f"正在校验答案...")
print(f"题目文件: {exercise_file}")
print(f"答案文件: {answer_file}")
# 读取题目和答案文件
questions = FileHandler.read_questions(exercise_file)
answers = FileHandler.read_answers(answer_file)
# 验证文件格式
if not FileHandler.validate_file_format(exercise_file, 'questions'):
Utils.print_error("题目文件格式错误", exit_code=EXIT_CODES['FORMAT_ERROR'])
if not FileHandler.validate_file_format(answer_file, 'answers'):
Utils.print_error("答案文件格式错误", exit_code=EXIT_CODES['FORMAT_ERROR'])
# 检查题目数量是否匹配
if len(questions) != len(answers):
Utils.print_error(
f"题目和答案数量不匹配,题目数量: {len(questions)}, 答案数量: {len(answers)}",
exit_code=EXIT_CODES['FORMAT_ERROR']
)
# 校验答案
correct_numbers = []
wrong_numbers = []
for (q_num, question), (a_num, answer) in zip(questions, answers):
if q_num != a_num:
Utils.print_error(
f"题目序号不匹配: 题目文件中第 {q_num} 题,答案文件中第 {a_num} 题",
exit_code=EXIT_CODES['FORMAT_ERROR']
)
# 计算标准答案
try:
correct_answer = self.calculator.calculate_expression(question)
formatted_correct = self.calculator.format_result(correct_answer)
# 比较答案
if self.compare_answers(answer, formatted_correct):
correct_numbers.append(q_num)
else:
wrong_numbers.append(q_num)
except Exception as e:
Utils.print_error(f"计算第 {q_num} 题时发生错误: {str(e)}", exit_code=EXIT_CODES['INTERNAL_ERROR'])
这个函数负责校验用户提供的答案是否正确,包括文件格式验证、题目数量匹配检查,以及逐题比较答案。
5. 括号插入算法
def insert_parentheses(expression_parts):
"""在表达式中插入括号,改变运算顺序"""
# 如果表达式少于1个运算符(2个操作数),不适合插入有意义的括号
if len(expression_parts) < 3: # 最少需要:operand operator operand
return " ".join(expression_parts)
# 将操作数和运算符分别提取
operands = []
operators = []
for i, part in enumerate(expression_parts):
if i % 2 == 0: # 偶数索引是操作数
operands.append(part)
else: # 奇数索引是运算符
operators.append(part)
n_operands = len(operands)
# 根据操作数数量选择合适的括号插入策略
if n_operands == 2: # a op b
return " ".join(expression_parts)
elif n_operands == 3: # a op b op c
# 40%概率添加括号
if random.random() < 0.6:
return " ".join(expression_parts)
# 选择括号模式:(a op b) op c 或 a op (b op c)
if random.random() < 0.5:
return f"({operands[0]} {operators[0]} {operands[1]}) {operators[1]} {operands[2]}"
else:
return f"{operands[0]} {operators[0]} ({operands[1]} {operators[1]} {operands[2]})"
# ... 更多情况的处理
这个函数根据表达式的复杂度和操作数数量,智能地在表达式中插入括号,增加题目的难度和多样性。
测试运行
测试用例1: 基本的题目生成
python main.py -n 10 -r 10
具体参数: 生成10道题目,数值范围1-10
预期结果: 生成包含10道整数四则运算题目的文件
实际结果示例:
1. 3 + 5
2. 7 - 2
3. 4 * 6
4. 8 / 2
5. 9 + 1
6. 10 - 4
7. 2 * 3
8. 6 / 3
9. 5 + 4
10. 8 - 1
测试用例2: 生成包含分数的题目
python main.py -n 5 -r 10 --fractions
具体参数: 生成5道可能包含分数的题目,数值范围1-10
预期结果: 生成混合整数和分数的题目
实际结果示例:
1. 1/2 + 3/4
2. 2 * 5/6
3. 1'1/2 - 1/3
4. 7/8 / 1/2
5. 3 + 1/4
测试用例3: 生成题目并自动填写答案
python main.py -n 3 -r 5 --with-answers
具体参数: 生成3道题目,数值范围1-5,自动生成答案
预期结果: 生成题目文件和包含正确答案的答案文件
实际结果示例:
题目文件:
1. 2 + 3
2. 4 - 1
3. 2 * 2
答案文件:
1. 5
2. 3
3. 4
测试用例4: 答案校验(正确答案)
# 首先生成带答案的题目
python main.py -n 2 -r 3 --with-answers
# 然后使用相同的答案文件进行校验
python main.py -e questions.txt -a answers.txt
具体参数: 校验2道题目的答案
预期结果: 所有答案校验通过,显示100%正确
实际结果示例:
正在校验答案...
题目文件: questions.txt
答案文件: answers.txt
校验完成:
正确: 2 题 (100.0%)
错误: 0 题 (0.0%)
总计: 2 题
正确题目的编号: [1, 2]
校验结果已保存到: results.txt
测试用例5: 答案校验(错误答案)
# 生成题目后,手动修改answers.txt中的部分答案
python main.py -n 2 -r 3 --with-answers
# 修改answers.txt,将第二题的答案从2改为5
# 然后进行校验
python main.py -e questions.txt -a answers.txt
具体参数: 校验包含错误答案的答案文件
预期结果: 错误的答案被检测出来
实际结果示例:
正在校验答案...
题目文件: questions.txt
答案文件: answers.txt
校验完成:
正确: 1 题 (50.0%)
错误: 1 题 (50.0%)
总计: 2 题
正确题目的编号: [1]
错误题目的编号: [2]
校验结果已保存到: results.txt
测试用例6: 参数错误处理
python main.py -n 10 # 缺少range参数
具体参数: 只提供题目数量,未提供数值范围
预期结果: 显示参数错误提示
实际结果示例:
错误: 参数错误:当使用 -n 参数时,必须同时使用 -r 参数
测试用例7: 生成大量题目
python main.py -n 1000 -r 20
具体参数: 生成1000道题目,数值范围1-20
预期结果: 生成1000道不重复的题目,性能良好
实际结果: 成功生成1000道题目,平均生成速度约1000题/秒
测试用例8: 带括号的复杂表达式
python main.py -n 3 -r 5
具体参数: 生成3道可能包含括号的题目
预期结果: 生成带括号的复杂表达式
实际结果示例:
1. (2 + 3) * 4
2. 5 - (1 + 2)
3. (4 * 2) / (1 + 1)
测试用例9: 边界值测试(range=1)
python main.py -n 5 -r 1
具体参数: 生成5道题目,数值范围仅限0和1
预期结果: 生成使用0和1的合法题目
实际结果示例:
1. 1 + 0
2. 1 - 0
3. 1 * 1
4. 1 / 1
5. 0 + 1
测试用例10: 分数边界值测试
python main.py -n 3 -r 4 --fractions
具体参数: 生成3道包含分数的题目,范围较小
预期结果: 生成合法的分数运算题目
实际结果示例:
1. 1/2 + 1/4
2. 3/4 - 1/2
3. 1'1/3 * 3/4
项目小结
成功之处
-
模块化设计:项目采用清晰的模块化结构,各模块职责明确,便于维护和扩展。
-
功能完整:实现了题目生成、答案计算、答案校验等核心功能,并支持分数运算和括号。
-
代码质量:代码结构清晰,注释完善,遵循Python编码规范。
-
错误处理:包含全面的错误处理机制,对各种异常情况进行了合理处理。
经验教训
-
需求分析的重要性:在开发初期,我们应该更仔细地分析需求,特别是关于分数运算的规则。
-
测试的重要性:测试应该贯穿整个开发过程,而不仅是在开发完成后进行。
-
性能考虑:在设计算法时,应该提前考虑性能因素,特别是对于可能生成大量题目的情况。
结对感受
通过这次结对项目,我们不仅完成了一个功能完整的四则运算题目生成器,更重要的是学会了如何有效地进行团队合作。在开发过程中,我们互相学习、互相补充,遇到问题时共同探讨解决方案,大大提高了开发效率和代码质量。
闪光点分享
-
徐新曜:在算法设计方面表现出色,特别是在分数处理和括号插入算法上提出了很多创新的思路。
-
许国伟:在代码质量和错误处理方面非常细致,确保了程序的稳定性和健壮性。
未来改进方向
- 添加更多类型的题目,如小数运算、混合运算等
- 支持自定义题目难度
- 添加图形用户界面,使程序更易于使用
- 优化性能,支持生成更多题目的情况
- 添加更多的测试用例,提高代码覆盖率