结对项目

基本信息

这个作业属于哪个课程 软件工程
这个作业要求在哪里 作业要求
成员 李炫杰(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

作业需求

题目:实现一个自动生成小学四则运算题目的命令行程序。

需求:

  1. 使用 -n 参数控制生成题目的个数,例如:Myapp.exe -n 10 将生成10个题目。

  2. 使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围,例如:Myapp.exe -r 10 将生成10以内(不包括10)的四则运算题目。该参数可以设置为1或其他自然数。该参数必须给定,否则程序报错并给出帮助信息。

  3. 生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1− e2的子表达式,那么e1≥ e2。

  4. 生成的题目中如果存在形如e1÷ e2的子表达式,那么其结果应是真分数。

  5. 每道题目中出现的运算符个数不超过3个。

  6. 程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。生成的题目存入执行程序的当前目录下的Exercises.txt文件。

  7. 在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件。

  8. 程序应能支持一万道题目的生成。

  9. 程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,输入参数如下: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

实现逻辑:

  • 处理方式:
  1. 生成两个随机操作数。
  2. 随机选择一个运算符。
  3. 如果是除法,则确保分母不为 0,并且结果可以整除。
  4. 通过交换律避免生成相同的加法或乘法表达式。
  • 特殊处理:对于加法和乘法,通过交换律确保不会生成重复表达式。对于除法,调用 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

实现逻辑:

  • 处理方式:
  1. 打开题目文件和答案文件。
  2. 逐行读取题目和答案,对每个题目使用 eval() 计算用户答案和正确答案。
  3. 比较用户答案和正确答案,记录正确或错误的题目编号。
  • 特殊处理:
  1. 使用 try-except 捕获可能出现的文件找不到错误和表达式评估错误,确保程序不崩溃。
  2. 使用 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.txttest_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编程的多个方面,包括文件操作和数学运算。与我的搭档结对工作,我们能够互补技能,共同解决问题,这不仅提高了效率,也增加了学习的乐趣。

郭梓佳:
本次的结对项目,我主要负责文档,报告,博客等方面的编写,辅助设计,编码的实现,记录我们的开发过程。这个经历提高了我的技术写作技能,并加深了我对项目细节的理解。与我的搭档结对工作,我们能够更好地协调信息流,确保了项目的透明度和沟通的流畅性。这种合作方式极大地提升了我的团队协作能力。

posted @ 2024-09-28 11:14  垚*  阅读(17)  评论(0编辑  收藏  举报