基本信息
这个作业属于哪个课程 | 软件工程 |
---|---|
这个作业要求在哪里 | 作业要求 |
成员 | 李炫杰(3122004953) 郭梓佳(3122004945) |
Github地址 | https://github.com/gzjgit-hub/pairing_project.git |
这个作业的目标 | 实现一个自动生成小学四则运算题目的命令行程序 |
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 30 |
· Estimate | · 估计这个任务需要多少时间 | 30 | 30 |
Development | 开发 | 790 | 880 |
· Analysis | · 需求分析 (包括学习新技术) | 60 | 70 |
· Design Spec | · 生成设计文档 | 60 | 60 |
· Design Review | · 设计复审 | 40 | 45 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 45 |
· Design | · 具体设计 | 180 | 200 |
· Coding | · 具体编码 | 240 | 260 |
· Code Review | · 代码复审 | 70 | 80 |
· Test | · 测试(自我测试,修改代码,提交修改) | 110 | 120 |
Reporting | 报告 | 130 | 80 |
· Test Repor | · 测试报告 | 60 | 30 |
· Size Measurement | · 计算工作量 | 40 | 30 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 20 |
· 合计 | 950 | 990 |
作业需求
题目:实现一个自动生成小学四则运算题目的命令行程序。
需求:
-
使用 -n 参数控制生成题目的个数,例如:Myapp.exe -n 10 将生成10个题目。
-
使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围,例如:Myapp.exe -r 10 将生成10以内(不包括10)的四则运算题目。该参数可以设置为1或其他自然数。该参数必须给定,否则程序报错并给出帮助信息。
-
生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1− e2的子表达式,那么e1≥ e2。
-
生成的题目中如果存在形如e1÷ e2的子表达式,那么其结果应是真分数。
-
每道题目中出现的运算符个数不超过3个。
-
程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。生成的题目存入执行程序的当前目录下的Exercises.txt文件。
-
在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件。
-
程序应能支持一万道题目的生成。
-
程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,输入参数如下:Myapp.exe -e
.txt -a .txt,统计结果输出到文件Grade.txt。
设计实现过程
程序架构图
程序执行流程逻辑图
模块设计
-
随机数和表达式生成模块:生成四则运算的随机操作数和表达式。
-
结果计算与转换模块:将计算结果转换为分数或带分数形式。
-
题目与答案生成模块:生成题目和相应答案并保存到文件中。
-
评估与统计模块:读取并评估用户的答案,并保存统计结果。
-
用户交互模块:处理用户输入,提供菜单选项,执行生成或评估的操作。
调用了哪些库和类
-
math:
用于进行数学操作。在此代码中主要用于 math.floor(),将浮点数向下取整,确保操作数的合法性,尤其是在乘法和加法的操作中,防止生成相同的表达式。 -
random:
用于生成随机数和随机选择。在此代码中,通过 random.randint() 生成随机的操作数,并通过 random.choice() 随机选择四则运算符(如加、减、乘、除)。 -
fractions.Fraction:
用于处理分数。Fraction 用于创建分数对象,允许精确的分数计算,尤其在除法操作中使用,确保结果是以分数表示而不是浮点数。
关键函数说明
1. create_random_number(value_limit):
随机生成一个自然数或真分数,用于构造四则运算表达式的操作数。
def create_random_number(value_limit):
if random.random() < 0.5: # 50% 的概率生成真分数
numer = random.randint(0, value_limit - 1)
denom = random.randint(numer + 1, value_limit)
return Fraction(numer, denom)
else: # 50% 的概率生成自然数
return random.randint(0, value_limit - 1)
实现逻辑:
- 处理方式:该函数根据 50% 的概率决定生成自然数或真分数,使用 random.random() 和 random.randint() 来生成随机数。
- 特殊处理:对于生成分数,确保分子小于分母,防止生成假分数或无效分数。
参数:
- value_limit: 限定生成数值的上限。
返回:
- 返回值:生成的自然数或者分数。
2. create_single_operation_expression(value_limit):
生成包含一个运算符的简单表达式(加、减、乘、除)。它确保除法不会产生无意义的结果,并且确保加法和乘法遵循交换律,避免重复的表达式。
# 生成包含一个运算符的简单表达式
def create_single_operation_expression(value_limit):
operand1 = create_random_number(value_limit)
operand2 = create_random_number(value_limit)
operator = random.choice(operation_symbols)
# 如果是除法,确保结果为真分数
if operator == '÷':
operand1, operand2 = ensure_proper_division(value_limit, operand1, operand2)
# 确保表达式不会出现0作为第一个操作数
while operand1 == 0:
operand1 = create_random_number(value_limit)
# 防止交换律导致生成相同的表达式
if operator in ('+', '×'):
operand1, operand2 = min(operand1, operand2), max(operand1, operand2)
if math.floor(operand2) == 0:
operand2 = random.randint(1, value_limit)
if min(operand1, operand2) == 0:
operand1 = random.randint(1, math.floor(operand2))
# 确保不会生成负数结果
if operator == '-':
operand1, operand2 = max(operand1, operand2), min(operand1, operand2)
# 表达式的两种不同格式
expr = f"{operand1} {operator} {operand2}"
expr_with_paren = f"({operand1}) {operator} ({operand2})"
return expr, expr_with_paren
实现逻辑:
- 处理方式:
- 生成两个随机操作数。
- 随机选择一个运算符。
- 如果是除法,则确保分母不为 0,并且结果可以整除。
- 通过交换律避免生成相同的加法或乘法表达式。
- 特殊处理:对于加法和乘法,通过交换律确保不会生成重复表达式。对于除法,调用 ensure_proper_division() 确保分母和分子的合法性。
参数:
- value_limit: 限定生成操作数的上限。
返回:
- 返回值:生成的表达式字符串(两种形式:普通和带括号的)。
3. create_arithmetic_expression(value_limit):
随机选择生成包含1至3个运算符的表达式,并且保证生成的表达式唯一(不重复)。
# 生成一个随机的表达式(包含1至3个运算符)
def create_arithmetic_expression(value_limit):
if value_limit < 1:
raise ValueError("数值范围必须大于等于1")
while True:
number_of_operators = random.randint(1, 3)
if number_of_operators == 1:
expr, expr_with_paren = create_single_operation_expression(value_limit)
elif number_of_operators == 2:
expr, expr_with_paren = create_two_operation_expression(value_limit)
else:
expr, expr_with_paren = create_three_operation_expression(value_limit)
# 确保生成的表达式是唯一的
if expr not in expression_history:
expression_history.add(expr)
return expr, expr_with_paren
实现逻辑:
- 处理方式:根据随机数生成一个包含 1 至 3 个运算符的表达式。调用不同的函数生成单个、两个或三个运算符的表达式。
- 特殊处理:使用集合 expression_history 存储已生成的表达式,确保生成的表达式是唯一的。
参数:
- value_limit: 限定生成操作数的上限。
返回:
- 返回值:生成的唯一表达式和带括号的表达式。
4. evaluate_answers(exercise_file, solution_file):
读取保存的题目和答案文件,评估用户提交的答案是否正确。通过比较用户答案与正确答案来记录正确和错误的题目编号。
# 统计题目的正确与错误数量
def evaluate_answers(exercise_file, solution_file):
correct_answers = []
incorrect_answers = []
try:
with open(exercise_file, "r", encoding='utf-8') as q_file, open(solution_file, "r", encoding='utf-8') as s_file:
for i, (q_line, s_line) in enumerate(zip(q_file, s_file), start=1):
q_line = q_line.strip()
s_line = s_line.strip()
if q_line.startswith("四则运算题目"):
q_parts = q_line.split(":", 1)
if len(q_parts) == 2:
q_line = q_parts[1].strip()
if s_line.startswith("答案"):
s_parts = s_line.split(":", 1)
if len(s_parts) == 2:
s_line = s_parts[1].strip()
try:
user_solution = eval(s_line.replace('‘', '+'))
user_solution = Fraction(user_solution).limit_denominator()
correct_solution = eval(wrap_with_parentheses(q_line).replace('÷', '/').replace('×', '*'))
correct_solution = Fraction(correct_solution).limit_denominator()
if user_solution == correct_solution:
correct_answers.append(i)
else:
incorrect_answers.append(i)
except Exception as err:
print(f"评估第 {i} 题时发生错误:{err}")
return correct_answers, incorrect_answers
except FileNotFoundError:
print("未找到文件")
return FileNotFoundError
实现逻辑:
- 处理方式:
- 打开题目文件和答案文件。
- 逐行读取题目和答案,对每个题目使用 eval() 计算用户答案和正确答案。
- 比较用户答案和正确答案,记录正确或错误的题目编号。
- 特殊处理:
- 使用 try-except 捕获可能出现的文件找不到错误和表达式评估错误,确保程序不崩溃。
- 使用 Fraction 保证结果是最简分数。
参数:
- exercise_file:题目文件名。
- solution_file:答案文件名。
返回:
- 返回值:两个列表,分别存储正确和错误的题目编号。
5. wrap_with_parentheses(expr_str):
为表达式的操作数添加括号,确保按照正确的运算顺序进行计算,尤其是复杂表达式中。
# 为所有数加上括号,确保正确的运算顺序
def wrap_with_parentheses(expr_str):
tokens = []
num_str = ''
for char in expr_str:
if char in '+-×÷':
tokens.append(num_str)
tokens.append(char)
num_str = ''
else:
num_str += char
tokens.append(num_str)
result_expr = ''
for i, token in enumerate(tokens):
if token in '+-×÷':
result_expr += token
else:
result_expr += '(' + token + ')'
return result_expr
实现逻辑:
- 处理方式:通过解析表达式字符串,为所有操作数添加括号,确保计算时的运算顺序正确。
参数:
- expr_str:表达式的字符串形式。
返回:
- 返回值:带括号的表达式字符串。
其余调用的函数及其作用
1. 随机数和表达式生成模块
create_two_operation_expression(value_limit):
生成包含两个运算符的表达式,确保生成的结果不会为负数。
# 生成包含两个运算符的表达式
def create_two_operation_expression(value_limit):
operand3 = create_random_number(value_limit)
operators_set = ['+', '-']
second_operator = random.choice(operators_set)
first_expr, first_expr_with_paren = create_single_operation_expression(value_limit)
# 避免负数结果
if second_operator == '-':
while eval(first_expr_with_paren.replace('÷', '/').replace('×', '*')) < operand3:
operand3 = create_random_number(value_limit)
expr = f"{first_expr} {second_operator} {operand3}"
expr_with_paren = f"{first_expr_with_paren} {second_operator} ({operand3})"
return expr, expr_with_paren
create_three_operation_expression(value_limit):
生成包含三个运算符的复杂表达式,同样确保不会生成负数结果。
# 生成包含三个运算符的复杂表达式
def create_three_operation_expression(value_limit):
operators_set_3 = ['+', '×', '÷']
third_operator = random.choice(operators_set_3)
first_expr, first_expr_with_paren = create_single_operation_expression(value_limit)
second_expr, second_expr_with_paren = create_single_operation_expression(value_limit)
expr = first_expr + ' ' + third_operator + ' ' + second_expr
expr_with_paren = first_expr_with_paren + third_operator + second_expr_with_paren
# 确保没有负数结果
while eval(expr_with_paren.replace('÷', '/').replace('×', '*')) < 0:
first_expr, first_expr_with_paren = create_single_operation_expression(value_limit)
second_expr, second_expr_with_paren = create_single_operation_expression(value_limit)
expr = first_expr + ' ' + third_operator + ' ' + second_expr
expr_with_paren = first_expr_with_paren + third_operator + second_expr_with_paren
return expr, expr_with_paren
2. 结果计算与转换模块
convert_decimal_to_fraction(decimal_val):
将计算出来的浮点数结果转换为最简分数形式。
# 将计算结果转换为最简分数
def convert_decimal_to_fraction(decimal_val):
return Fraction(decimal_val).limit_denominator()
convert_improper_fraction(fraction_str):
将假分数转换为带分数,便于结果的展示。
# 将假分数转换为带分数
def convert_improper_fraction(fraction_str):
numer, denom = map(int, fraction_str.split('/'))
if numer >= denom:
integer_part = numer // denom
remainder = numer % denom
if remainder == 0:
return str(integer_part)
else:
return f"{integer_part}‘{remainder}/{denom}"
else:
return fraction_str
3. 题目与答案生成模块
generate_questions_and_solutions(question_count, value_limit):
根据指定的题目数量和数值范围,生成相应的四则运算题目,并计算其答案。答案被转换为分数,并存储在列表中。
# 生成题目和相应的答案
def generate_questions_and_solutions(question_count, value_limit):
questions = []
solutions = []
for _ in range(question_count):
expr, expr_with_paren = create_arithmetic_expression(value_limit)
decimal_result = eval(expr_with_paren.replace('÷', '/').replace('×', '*'))
fraction_result = convert_decimal_to_fraction(decimal_result)
if isinstance(fraction_result, int) == False and fraction_result % 1 != 0:
fraction_result = convert_improper_fraction(f"{Fraction(fraction_result).limit_denominator()}")
questions.append(expr)
solutions.append(fraction_result)
return questions, solutions
save_to_file(questions, solutions):
将生成的题目和对应的答案保存到文本文件中,分别为 Exercises.txt 和 Answers.txt。
# 将生成的题目和答案保存到文件中,文件名为 Exercises.txt 和 Answers.txt
def save_to_file(questions, solutions):
with open("Exercises.txt", "w", encoding='utf-8') as exercise_file, open("Answers.txt", "w", encoding='utf-8') as answer_file:
for i, (q, a) in enumerate(zip(questions, solutions), start=1):
exercise_file.write(f"四则运算题目{i}: {q}\n")
answer_file.write(f"答案{i}: {a}\n")
4. 评估与统计模块
save_evaluation_results(correct_answers, incorrect_answers):
将评估的正确与错误的题目编号保存到 Grade.txt 文件中。
# 将评分结果保存到文件,文件名为 Grade.txt,按照指定格式
def save_evaluation_results(correct_answers, incorrect_answers):
with open("Grade.txt", "w") as results_file:
results_file.write(f"Correct: {len(correct_answers)} ({', '.join(map(str, correct_answers))})\n")
results_file.write(f"Wrong: {len(incorrect_answers)} ({', '.join(map(str, incorrect_answers))})\n")
5. 用户交互模块
主程序部分 (if name == "main"):
提供一个简单的菜单,用户可以选择生成题目或评估答案。
如果选择生成题目,程序会提示用户输入题目数量和数值范围,然后调用生成题目和答案的相关函数。
如果选择评估答案,程序会读取题目和答案文件,进行评估并输出结果。
if __name__ == "__main__":
# 改进的功能菜单,增加独特性
print("欢迎使用智能算术题生成与评估系统")
print("请选择功能:\n1. 生成算术题\n2. 统计答题正确率\n")
user_choice = int(input("请输入对应的数字进行选择:"))
if user_choice == 1:
question_count = int(input("请输入需要生成的题目数量:"))
value_range = int(input("请输入数值范围:"))
questions, solutions = generate_questions_and_solutions(question_count, value_range)
save_to_file(questions, solutions)
print(f"已生成 {question_count} 道题目,并保存至 Exercises.txt 和 Answers.txt 文件中")
elif user_choice == 2:
exercise_filename = input("请输入题目文件名(例如 Exercises.txt):")
answer_filename = input("请输入答案文件名(例如 Answers.txt):")
correct_answers, incorrect_answers = evaluate_answers(exercise_filename, answer_filename)
save_evaluation_results(correct_answers, incorrect_answers)
print("答题统计已保存至 Grade.txt 文件中")
结果展示
生成十道题目后的结果 Exercises.txt和Answers.txt
结果评估
修改前三道题目答案后检验的结果
性能分析
采用pycharm中自带的profile分析性能插件进行分析
图中展示函数之间的调用关系、调用次数和执行时间等信息。
模块部分单元测试展示
测试结果
1.测试生成随机数的功能:test_create_random_number
def test_create_random_number(self):
# 正常范围测试
for _ in range(100):
number = create_random_number(10)
self.assertTrue(0 <= number < 10)
# 边界测试:数值范围为0或1
with self.assertRaises(ValueError):
create_random_number(0) # 处理范围为0的情况
self.assertTrue(0 <= create_random_number(1) <= 1) # 范围为1时只能生成0或1
逻辑:
- 这个测试主要用于验证
create_random_number
函数的行为。 - 第一部分生成了 100 个随机数,确保生成的数都在
[0, value_limit)
的范围内,这里value_limit
为 10。 - 第二部分进行边界测试,检查当
value_limit
为 0 或 1 时的行为:- 当
value_limit
为 0 时,函数应当抛出ValueError
,因为生成的数无法在该范围内。 - 当
value_limit
为 1 时,生成的数应在[0, 1]
范围内。
- 当
意图:
确保 create_random_number
函数能够正确生成符合范围的随机数,并在边界条件下正确处理错误。
作用:
验证生成随机数的功能,保证数值范围符合预期,并避免可能出现的错误输入
2.测试生成的表达式是否有重复:test_create_arithmetic_expression_uniqueness
def test_create_arithmetic_expression_uniqueness(self):
value_limit = 10
num_expressions = 1000
generated_expressions = set()
for _ in range(num_expressions):
expression, _ = create_arithmetic_expression(value_limit)
self.assertNotIn(expression, generated_expressions, f"发现重复表达式: {expression}")
generated_expressions.add(expression)
逻辑:
- 生成 1000 个表达式,并将它们存入一个集合
generated_expressions
。 - 对于每个生成的表达式,检查它是否已存在于集合中。如果存在,说明生成了重复表达式。
- 如果生成了重复的表达式,则测试失败。
意图:
确保 create_arithmetic_expression
函数能够生成唯一的表达式,不会出现重复的情况。
作用:
检测表达式生成器的唯一性,确保每个生成的表达式是不同的,避免生成相同或等效的表达式。
3.测试将小数转换为分数:test_convert_decimal_to_fraction
def test_convert_decimal_to_fraction(self):
decimal_values = [0.25, 0.5, 0.75, 1.2, 2.5]
expected_fractions = [Fraction(1, 4), Fraction(1, 2), Fraction(3, 4), Fraction(6, 5), Fraction(5, 2)]
for decimal, expected in zip(decimal_values, expected_fractions):
result = convert_decimal_to_fraction(decimal)
self.assertEqual(result, expected)
逻辑:
- 这个测试将一组小数值转换为分数,并与预期结果进行对比。
- 使用
convert_decimal_to_fraction
函数将小数转换为最简分数。 - 验证转换结果是否与预期的分数一致。
意图:
确保 convert_decimal_to_fraction
函数能够正确将小数转换为最简分数。
作用:
测试小数到分数的转换功能,保证数值转换的正确性,特别是处理有理数的场景。
4. 测试将假分数转换为带分数:test_convert_improper_fraction
def test_convert_improper_fraction(self):
test_cases = {
'5/3': '1‘2/3',
'34/11': '3‘1/11',
'59/8': '7‘3/8',
'2612/315': '8‘92/315',
'383/40': '9‘23/40'
}
for improper, expected in test_cases.items():
result = convert_improper_fraction(improper)
self.assertEqual(result, expected)
逻辑:
- 这个测试将一组假分数转换为带分数。
- 使用
convert_improper_fraction
函数将假分数转换为带分数格式,并与预期结果进行比较。
意图:
确保 convert_improper_fraction
函数能够正确地将假分数转换为带分数,并且格式正确。
作用:
验证带分数转换的功能,保证在数值过大或不符合直观表现的情况下,能够准确显示带分数。
5. 测试为表达式中的所有数加括号:test_wrap_with_parentheses
def test_wrap_with_parentheses(self):
test_cases = {
"1 - 0 ÷ 4/5 - 3/7": "(1 )-( 0 )÷( 4/5 )-( 3/7)",
"7 + 2 - 4 ÷ 3/7": "(7 )+( 2 )-( 4 )÷( 3/7)",
"1/4 ÷ 2 - 6/7": "(1/4 )÷( 2 )-( 6/7)",
"4/5 × 6 + 1/3": "(4/5 )×( 6 )+( 1/3)",
"2 × 5/8": "(2 )×( 5/8)"
}
for expression, expected in test_cases.items():
result = wrap_with_parentheses(expression)
self.assertEqual(result, expected)
逻辑:
- 为给定的数学表达式中所有的数添加括号,确保每个数都被括起来,以保证表达式的运算顺序。
- 比较每个表达式的输出是否与预期的带括号格式一致。
意图:
验证 wrap_with_parentheses
函数能够正确地为表达式中的所有数字加括号,以保证运算顺序。
作用:
确保表达式在添加括号后仍然符合数学运算的顺序要求,避免因优先级问题导致的错误结果。
6. 测试统计题目正确率的函数:test_evaluate_answers
def test_evaluate_answers(self):
correct_indices, wrong_indices = evaluate_answers("test_Exercises.txt", "test_Answers.txt")
self.assertEqual(len(correct_indices), 100)
self.assertEqual(len(wrong_indices), 0)
逻辑:
- 这个测试检查
evaluate_answers
函数能否正确统计答案的正确和错误的数量。 - 读取测试文件
test_Exercises.txt
和test_Answers.txt
,统计正确和错误的答案数量。 - 比较统计结果,确保有 100 个正确答案,0 个错误答案。
意图:
确保 evaluate_answers
函数能够正确评估题目并统计正确答案和错误答案。
作用:
测试评估系统的正确性,确保答案文件能够正确匹配题目文件。
7. 测试文件路径不存在的情况:test_invalid_file_paths
def test_invalid_file_paths(self):
self.assertEqual(evaluate_answers('不存在的文件.txt', '不存在的文件.txt'), FileNotFoundError)
逻辑:
- 测试当题目或答案文件不存在时,
evaluate_answers
函数能否正确处理并抛出FileNotFoundError
。 - 传入不存在的文件路径,检查是否抛出了相应的错误。
意图:
确保在文件路径不正确或文件不存在时,程序能给出正确的异常反馈。
作用:
测试文件处理的健壮性,确保在找不到文件的情况下程序不会崩溃。
8. 测试生成表达式是否合法:test_create_arithmetic_expression
def test_create_arithmetic_expression(self):
for _ in range(100):
expression, expression_with_paren = create_arithmetic_expression(10)
try:
result = eval(expression_with_paren.replace('÷', '/').replace('×', '*'))
self.assertIsInstance(result, (int, float))
except ZeroDivisionError:
self.fail("生成了包含除以零的表达式")
逻辑:
- 生成 100 个表达式,检查表达式的合法性。
- 使用
eval
评估表达式,确保其结果是整数或浮点数。 - 捕获可能的
ZeroDivisionError
(除以零),如果出现该错误,测试失败。
意图:
确保 create_arithmetic_expression
生成的表达式合法,并且不会出现除以零的情况。
作用:
验证表达式生成器的合法性,避免生成无法计算或非法的表达式。
10. 边界测试:极大数值范围:test_large_value_limit
def test_large_value_limit(self):
value_limit = 10**6 # 设置极大范围
for _ in range(10):
expression, expression_with_paren = create_arithmetic_expression(value_limit)
try:
result = eval(expression_with_paren.replace('÷', '/').replace('×', '*'))
self.assertIsInstance(result, (int, float))
except ZeroDivisionError:
self.fail("生成了包含除以零的表达式")
逻辑:
- 使用非常大的
value_limit
(1,000,000)进行边界测试,确保表达式生成器在处理大数值时仍能正常工作。 - 检查表达式的合法性,避免除以零。
意图:
验证系统在极大数值范围下的稳定性,确保生成的表达式在大范围内仍然有效。
作用:
测试极大数值范围下的表达式生成,确保不会因大数值导致错误。
模块部分异常值处理
(1) 确保分数除法合法性 (ensure_proper_division)
在 ensure_proper_division 函数中,代码确保除法操作合法。主要检查的两个条件:
- 分母不能为零。
- 分子必须能够被分母整除,确保除法的结果是一个真分数或整除结果。
异常处理逻辑:
使用 while 循环不断生成新的随机分子和分母,直到分母不为零且可以整除分子。这是一种简单有效的防止非法除法操作的方式。
while divisor == 0 or dividend % divisor != 0:
dividend = create_random_number(value_limit)
divisor = create_random_number(value_limit)
(2) 评估用户答案时的异常处理 (evaluate_answers)
在评估用户的答案时,有几种可能的异常需要处理,例如:
- 文件找不到 (FileNotFoundError)。
- 评估表达式时可能出现的错误(例如无效的表达式,非法运算符等)。
代码中通过 try-except 结构捕获了这些可能的异常,确保程序在出现异常时不会崩溃。
# 捕获文件未找到的异常
except FileNotFoundError:
print("未找到文件")
return FileNotFoundError
# 捕获表达式评估中的错误
try:
user_solution = eval(s_line.replace('‘', '+'))
user_solution = Fraction(user_solution).limit_denominator()
correct_solution = eval(wrap_with_parentheses(q_line).replace('÷', '/').replace('×', '*'))
correct_solution = Fraction(correct_solution).limit_denominator()
if user_solution == correct_solution:
correct_answers.append(i)
else:
incorrect_answers.append(i)
except Exception as err:
print(f"评估第 {i} 题时发生错误:{err}")
详细说明:
- FileNotFoundError: 如果用户提供的题目文件或答案文件不存在,程序会捕获该异常并输出相应的提示信息,而不是直接抛出错误并终止程序。
- Exception: 在评估表达式时,可能会出现语法错误或非法操作。为了避免程序崩溃,使用了一个通用的 except Exception 捕获所有潜在的错误,并打印出错误信息,同时继续评估后续题目。
(3) 处理浮点数转分数 (convert_decimal_to_fraction)
在计算表达式结果时,可能会生成浮点数。这时,convert_decimal_to_fraction 函数会将结果转换为最简分数。这是为了避免浮点精度问题,并确保计算结果以分数的形式输出。
没有使用异常处理机制,但通过使用 Fraction(decimal_val).limit_denominator() 确保结果是合法且最简的分数。
def convert_decimal_to_fraction(decimal_val):
return Fraction(decimal_val).limit_denominator()
(4) 文件写入的安全性 (save_to_file 和 save_evaluation_results)
在保存题目和答案、保存评估结果时,代码使用 with 语句来确保文件在写入操作完成后被正确关闭,避免了资源泄露的风险。
with open("Exercises.txt", "w", encoding='utf-8') as exercise_file, open("Answers.txt", "w", encoding='utf-8') as answer_file:
···
这种做法是一种常见的防止异常情况下(如程序中途退出或出现意外错误)文件未被正确关闭的处理方式,属于隐式的异常处理机制。
项目小结
李炫杰:
我主要负责具体设计,具体编码和测试等方面,实现了智能算术题生成与评估系统。这个系统满足了作业的需求,能够随机生成四则运算题,并评估答案。同时实现了生成唯一表达式、确保除法结果为分数、以及将题目和答案保存到文件的功能。这个项目让我更加深入探索了Python编程的多个方面,包括文件操作和数学运算。与我的搭档结对工作,我们能够互补技能,共同解决问题,这不仅提高了效率,也增加了学习的乐趣。
郭梓佳:
本次的结对项目,我主要负责文档,报告,博客等方面的编写,辅助设计,编码的实现,记录我们的开发过程。这个经历提高了我的技术写作技能,并加深了我对项目细节的理解。与我的搭档结对工作,我们能够更好地协调信息流,确保了项目的透明度和沟通的流畅性。这种合作方式极大地提升了我的团队协作能力。