结对项目

小学四则运算题目生成程序设计方案

项目 内容
这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/homework/13479
这个作业的目标 实现一个小自动生成小学四则运算题目的程序
github仓库链接 ZzzzHzH/Paired-project
成员1:张滨皓 3123004504
成员2:柯程炜 3123004486

一、项目概述

本项目旨在实现一个自动生成小学四则运算题目的命令行程序,支持生成指定数量和数值范围的题目,同时能够对题目和答案进行判分。

二、需求分析

  1. 支持生成自然数和真分数的四则运算(+、-、×、÷)题目
  2. 支持括号运算
  3. 通过 - n 参数控制题目数量,-r 参数控制数值范围(必须提供)
  4. 生成的题目需满足:
    • 减法运算结果不能为负数
    • 除法运算结果应为真分数
    • 每道题运算符不超过 3 个
    • 题目不能重复(考虑交换律等等价变换)
  5. 题目和答案分别存入 Exercises.txt 和 Answers.txt
  6. 支持通过 - e 和 - a 参数对题目和答案进行判分,结果存入 Grade.txt

三、程序设计

3.1 数据结构设计

  1. 分数类(Fraction)

    • 处理分数的表示、运算和格式化输出
    • 属性:分子、分母、整数部分(用于带分数)
    • 方法:加、减、乘、除、化简、比较大小、转换为字符串
  2. 表达式类(Expression)

    • 表示一个算术表达式
    • 属性:表达式字符串、运算符列表、操作数列表、结果
    • 方法:生成表达式、计算结果、标准化(用于去重)、转换为字符串

3.2 核心模块设计

  1. 参数解析模块:解析命令行参数,判断程序运行模式
  2. 题目生成模块:生成符合要求的四则运算题目
  3. 表达式计算模块:计算表达式的结果
  4. 去重模块:确保生成的题目不重复
  5. 文件操作模块:读写题目、答案和评分结果
  6. 判分模块:比对题目和答案,生成评分结果

3.3 程序流程图

开始
  |
  |-- 解析命令行参数
  |     |
  |     |-- 生成题目模式(-n和-r)
  |     |     |
  |     |     |-- 验证参数有效性
  |     |     |-- 生成指定数量的题目
  |     |     |     |-- 生成随机表达式
  |     |     |     |-- 检查是否符合规则(无负数、除法有效等)
  |     |     |     |-- 检查是否重复
  |     |     |     |-- 计算答案
  |     |     |     |-- 添加到题目列表
  |     |     |-- 将题目写入Exercises.txt
  |     |     |-- 将答案写入Answers.txt
  |     |
  |     |-- 判分模式(-e和-a)
  |           |
  |           |-- 读取题目文件和答案文件
  |           |-- 对每个题目计算答案
  |           |-- 与提供的答案比较
  |           |-- 统计正确和错误的题目
  |           |-- 将结果写入Grade.txt
  |
  结束

四、代码实现

4.1 分数类实现

import math

class Fraction:
    def __init__(self, numerator=0, denominator=1, integer_part=0):
        """初始化分数,支持整数、纯分数和带分数"""
        # 确保分母为正数
        if denominator < 0:
            numerator = -numerator
            denominator = -denominator
            
        # 处理整数部分
        if integer_part != 0:
            numerator += abs(integer_part) * denominator
            if integer_part < 0:
                numerator = -numerator
            integer_part = 0
            
        self.numerator = numerator
        self.denominator = denominator
        self.integer_part = integer_part
        self.simplify()
    
    def simplify(self):
        """化简分数"""
        if self.denominator == 0:
            raise ValueError("分母不能为零")
            
        # 处理分子为零的情况
        if self.numerator == 0:
            self.integer_part = 0
            self.denominator = 1
            return
            
        # 计算最大公约数
        gcd = math.gcd(abs(self.numerator), self.denominator)
        self.numerator //= gcd
        self.denominator //= gcd
        
        # 分离整数部分
        if abs(self.numerator) >= self.denominator:
            self.integer_part = self.numerator // self.denominator
            self.numerator = self.numerator % self.denominator
            
            # 确保分子为正数
            if self.numerator < 0:
                self.numerator = -self.numerator
                self.integer_part += 1 if self.integer_part < 0 else -1
    
    def __add__(self, other):
        """加法运算"""
        new_num = self.to_improper().numerator * other.denominator + other.to_improper().numerator * self.denominator
        new_den = self.denominator * other.denominator
        return Fraction(new_num, new_den)
    
    def __sub__(self, other):
        """减法运算"""
        new_num = self.to_improper().numerator * other.denominator - other.to_improper().numerator * self.denominator
        new_den = self.denominator * other.denominator
        return Fraction(new_num, new_den)
    
    def __mul__(self, other):
        """乘法运算"""
        new_num = self.to_improper().numerator * other.to_improper().numerator
        new_den = self.denominator * other.denominator
        return Fraction(new_num, new_den)
    
    def __truediv__(self, other):
        """除法运算"""
        new_num = self.to_improper().numerator * other.denominator
        new_den = self.denominator * other.to_improper().numerator
        return Fraction(new_num, new_den)
    
    def __ge__(self, other):
        """大于等于比较"""
        return self.to_improper().numerator * other.denominator >= other.to_improper().numerator * self.denominator
    
    def to_improper(self):
        """转换为假分数"""
        if self.integer_part == 0:
            return Fraction(self.numerator, self.denominator)
        return Fraction(self.numerator + abs(self.integer_part) * self.denominator, 
                       self.denominator, 
                       1 if self.integer_part > 0 else -1)
    
    def __str__(self):
        """转换为字符串表示"""
        if self.numerator == 0:
            return f"{self.integer_part}"
        
        if self.integer_part != 0:
            return f"{self.integer_part}'{abs(self.numerator)}/{self.denominator}"
        else:
            return f"{self.numerator}/{self.denominator}"
    
    def __eq__(self, other):
        """判断两个分数是否相等"""
        if not isinstance(other, Fraction):
            return False
        self_imp = self.to_improper()
        other_imp = other.to_improper()
        return self_imp.numerator * other_imp.denominator == other_imp.numerator * self_imp.denominator

4.2 表达式生成与计算

import random
import re

class ExpressionGenerator:
    def __init__(self, range_limit):
        self.range_limit = range_limit
        self.operators = ['+', '-', '×', '÷']
        self.generated_expressions = set()  # 用于存储已生成的表达式的标准化形式,防止重复
    
    def generate_number(self):
        """生成一个随机数(自然数或真分数)"""
        is_fraction = random.choice([True, False])
        
        if not is_fraction:
            # 生成自然数
            return Fraction(integer_part=random.randint(0, self.range_limit - 1))
        else:
            # 生成真分数
            denominator = random.randint(2, self.range_limit - 1)
            numerator = random.randint(1, denominator - 1)
            
            # 有一定概率生成带分数
            if random.random() < 0.3 and self.range_limit > 1:
                integer_part = random.randint(1, self.range_limit - 1)
                return Fraction(numerator, denominator, integer_part)
            else:
                return Fraction(numerator, denominator)
    
    def generate_expression(self, max_ops=3):
        """生成一个表达式"""
        if max_ops == 0 or random.random() < 0.3:  # 30%的概率生成一个原子表达式
            return str(self.generate_number())
        
        # 否则生成一个复合表达式
        op_count = random.randint(1, max_ops)
        op = random.choice(self.operators)
        
        # 递归生成左右子表达式
        left_max_ops = random.randint(0, op_count - 1)
        right_max_ops = op_count - 1 - left_max_ops
        
        left_expr = self.generate_expression(left_max_ops)
        right_expr = self.generate_expression(right_max_ops)
        
        # 随机添加括号
        if random.random() < 0.2:
            left_expr = f"({left_expr})"
        if random.random() < 0.2:
            right_expr = f"({right_expr})"
        
        expr_str = f"{left_expr} {op} {right_expr}"
        
        # 检查表达式是否有效(无负数结果,除法有效)
        try:
            result = self.evaluate_expression(expr_str)
            
            # 检查减法是否产生负数
            if '-' in expr_str and not self.check_subtraction_validity(expr_str):
                return self.generate_expression(max_ops)
                
            # 检查除法结果是否为真分数
            if '÷' in expr_str and not self.check_division_validity(expr_str):
                return self.generate_expression(max_ops)
                
            # 检查是否重复
            normalized = self.normalize_expression(expr_str)
            if normalized in self.generated_expressions:
                return self.generate_expression(max_ops)
                
            self.generated_expressions.add(normalized)
            return expr_str
        except:
            return self.generate_expression(max_ops)
    
    def evaluate_expression(self, expr_str):
        """计算表达式的值"""
        # 将表达式中的符号替换为Python可识别的符号
        python_expr = expr_str.replace('×', '*').replace('÷', '/')
        
        # 解析分数
        def parse_fraction(match):
            if "'" in match.group():
                # 带分数
                integer_part, fraction_part = match.group().split("'")
                numerator, denominator = fraction_part.split("/")
                return f"({integer_part}) + ({numerator})/({denominator})"
            elif "/" in match.group():
                # 纯分数
                numerator, denominator = match.group().split("/")
                return f"({numerator})/({denominator})"
            else:
                # 整数
                return match.group()
        
        # 将表达式中的分数转换为Python表达式
        python_expr = re.sub(r"\d+'\d+/\d+|\d+/\d+|\d+", parse_fraction, python_expr)
        
        # 计算结果
        result = eval(python_expr)
        
        # 将结果转换为Fraction对象
        if isinstance(result, float):
            # 处理浮点数,转换为分数
            numerator = result * 10**10
            denominator = 10**10
            return Fraction(int(numerator), int(denominator))
        else:
            return Fraction(integer_part=int(result))
    
    def check_subtraction_validity(self, expr_str):
        """检查减法是否有效(结果非负)"""
        # 这里简化处理,实际应递归检查所有子表达式
        # 分割表达式,计算左右两边的值
        # 注意:这是一个简化实现,实际需要更复杂的表达式解析
        try:
            # 找到最外层的减法运算符
            # 这只是一个简化的实现
            parts = expr_str.split(" - ")
            if len(parts) == 2:
                left_val = self.evaluate_expression(parts[0])
                right_val = self.evaluate_expression(parts[1])
                return left_val >= right_val
            return True
        except:
            return False
    
    def check_division_validity(self, expr_str):
        """检查除法是否有效(结果为真分数)"""
        # 简化处理,实际应检查所有除法子表达式
        try:
            result = self.evaluate_expression(expr_str)
            # 任何分数都是有效的,因为Fraction类会处理
            return True
        except:
            return False
    
    def normalize_expression(self, expr_str):
        """标准化表达式,用于去重"""
        # 处理加法和乘法的交换律
        # 这是一个简化实现,实际需要更复杂的处理
        if '+' in expr_str and not re.search(r'\([^)]*\+[^)]*\)', expr_str):
            parts = expr_str.split(" + ")
            if len(parts) == 2:
                return " + ".join(sorted(parts))
        
        if '×' in expr_str and not re.search(r'\([^)]*×[^)]*\)', expr_str):
            parts = expr_str.split(" × ")
            if len(parts) == 2:
                return " × ".join(sorted(parts))
        
        return expr_str

4.3 主程序实现

import argparse
import os

def generate_exercises(num, range_limit):
    """生成指定数量的题目"""
    generator = ExpressionGenerator(range_limit)
    exercises = []
    answers = []
    
    for _ in range(num):
        expr = generator.generate_expression(3)  # 最多3个运算符
        exercises.append(f"{expr} =")
        answer = generator.evaluate_expression(expr)
        answers.append(str(answer))
    
    # 写入文件
    with open("Exercises.txt", "w", encoding="utf-8") as f:
        for i, exercise in enumerate(exercises, 1):
            f.write(f"{i}. {exercise}\n")
    
    with open("Answers.txt", "w", encoding="utf-8") as f:
        for i, answer in enumerate(answers, 1):
            f.write(f"{i}. {answer}\n")

def grade_exercises(exercise_file, answer_file):
    """对题目和答案进行判分"""
    # 读取题目文件
    with open(exercise_file, "r", encoding="utf-8") as f:
        exercises = [line.strip() for line in f if line.strip()]
    
    # 读取答案文件
    with open(answer_file, "r", encoding="utf-8") as f:
        answers = [line.strip() for line in f if line.strip()]
    
    if len(exercises) != len(answers):
        raise ValueError("题目数量和答案数量不匹配")
    
    correct = []
    wrong = []
    generator = ExpressionGenerator(100)  # 范围不影响判分
    
    for i in range(len(exercises)):
        # 提取题目表达式(去除序号和等号)
        expr = re.sub(r'^\d+\. ', '', exercises[i]).replace(" =", "")
        # 提取答案(去除序号)
        user_answer = re.sub(r'^\d+\. ', '', answers[i])
        
        # 计算正确答案
        try:
            correct_answer = str(generator.evaluate_expression(expr))
            
            # 比较答案
            if correct_answer == user_answer:
                correct.append(i + 1)  # 题目编号从1开始
            else:
                wrong.append(i + 1)
        except:
            wrong.append(i + 1)
    
    # 写入评分结果
    with open("Grade.txt", "w", encoding="utf-8") as f:
        f.write(f"Correct: {len(correct)} ({', '.join(map(str, correct))})\n")
        f.write(f"Wrong: {len(wrong)} ({', '.join(map(str, wrong))})\n")

def main():
    parser = argparse.ArgumentParser(description='小学四则运算题目生成器')
    parser.add_argument('-n', type=int, help='生成题目的数量')
    parser.add_argument('-r', type=int, help='题目中数值的范围')
    parser.add_argument('-e', help='题目文件路径')
    parser.add_argument('-a', help='答案文件路径')
    
    args = parser.parse_args()
    
    # 判断运行模式
    if args.n is not None and args.r is not None:
        # 生成题目模式
        if args.r < 1:
            print("错误:范围参数r必须是大于等于1的自然数")
            return
        generate_exercises(args.n, args.r)
    elif args.e is not None and args.a is not None:
        # 判分模式
        if not os.path.exists(args.e):
            print(f"错误:题目文件{args.e}不存在")
            return
        if not os.path.exists(args.a):
            print(f"错误:答案文件{args.a}不存在")
            return
        grade_exercises(args.e, args.a)
    else:
        # 参数错误
        print("参数错误,请使用正确的参数组合:")
        print("生成题目:Myapp.exe -n 题目数量 -r 范围")
        print("判分:Myapp.exe -e 题目文件 -a 答案文件")
        parser.print_help()

if __name__ == "__main__":
    main()

五、测试用例

  1. 基本功能测试:

     Myapp.exe -n 10 -r 10
    

    验证是否生成 10 道 10 以内的题目和对应的答案。

  2. 范围测试:

    Myapp.exe -n 5 -r 5
    

    验证生成的题目中数值是否都在 5 以内。

  3. 大量题目测试:

    Myapp.exe -n 10000 -r 20
    

    验证程序是否能支持生成 1 万道题目。

  4. 判分功能测试:

    Myapp.exe -e Exercises.txt -a Answers.txt
    

    验证判分功能是否正常,结果是否正确。

  5. 分数运算测试:

    检查生成的分数题目是否正确,如 "1/2 + 1/3 =" 的答案是否为 "5/6"。

  6. 减法测试:

    检查减法题目是否满足被减数大于等于减数,如不会出现 "3 - 5 =" 这样的题目。

  7. 除法测试:

    检查除法题目结果是否为真分数,如 "1 ÷ 2 =" 的答案应为 "1/2"。

  8. 括号测试:

    检查带括号的题目是否正确,如 "(3 + 5) × 2 =" 的答案应为 "16"。

  9. 重复题目测试:

    生成多道题目,检查是否有重复的题目。

  10. 参数错误测试:

    Myapp.exe -n 10
    Myapp.exe -r 10
    Myapp.exe -e Exercises.txt
    

    验证程序在参数错误时是否能给出正确的提示。

六、PSP 表格

PSP2.1 预估耗时(分钟) 实际耗时(分钟)
计划 30 40
・估计任务时间 30 40
开发 480 540
・需求分析 60 70
・生成设计文档 40 50
・设计复审 30 30
・代码规范 20 20
・具体设计 60 70
・具体编码 200 220
・代码复审 40 50
・测试 30 50
报告 60 70
・测试报告 20 20
・计算工作量 10 10
・事后总结 30 40
合计 570 650

七、项目小结

本项目实现了一个功能完整的小学四则运算题目生成程序,支持题目生成和答案判分功能。在实现过程中,主要挑战在于:

  1. 分数的表示和运算:需要正确处理真分数、带分数的各种运算。
  2. 表达式的生成和去重:需要确保生成的表达式符合要求,并且不重复。
  3. 表达式的解析和计算:需要正确解析包含分数和括号的表达式并计算结果。

通过合理的类设计和模块化实现,我们成功解决了这些挑战。程序的扩展性较好,可以方便地添加新的功能,如支持更多类型的题目或更复杂的运算。

在结对开发过程中,团队成员之间的沟通和协作非常重要。通过分工合作,我们能够高效地完成各个模块的开发,并通过代码复审发现和解决潜在的问题。同时,通过互相学习和讨论,我们也提升了自身的编程能力和问题解决能力。

未来可以考虑添加更多功能,如生成不同难度级别的题目、支持小数运算、添加图形界面等,使程序更加完善和易用。

posted @ 2025-10-22 22:47  ZzzzHzH  阅读(0)  评论(0)    收藏  举报