From 49b3c7964040d4d9ea877c32a4474294d9dba114 Mon Sep 17 00:00:00 2001 From: xuan <1505557421@qq.com> Date: Tue, 23 Sep 2025 22:10:28 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=EF=BC=9A=E4=B8=AD=E5=B0=8F=E5=AD=A6=E6=95=B0=E5=AD=A6=E5=8D=B7?= =?UTF-8?q?=E5=AD=90=E8=87=AA=E5=8A=A8=E7=94=9F=E6=88=90=E7=A8=8B=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/README.md | 269 ++++++++++++++++++++++++++++++++ requirements.txt | 2 + src/main.py | 397 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 668 insertions(+) create mode 100644 doc/README.md create mode 100644 requirements.txt create mode 100644 src/main.py diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..c282131 --- /dev/null +++ b/doc/README.md @@ -0,0 +1,269 @@ +# 中小学数学卷子自动生成程序 - 详细说明文档 + +## 目录 +1. [项目概述](#项目概述) +2. [系统架构](#系统架构) +3. [功能详解](#功能详解) +4. [使用指南](#使用指南) +5. [技术实现](#技术实现) +6. [测试用例](#测试用例) +7. [部署说明](#部署说明) + +## 项目概述 + +本程序是一个面向中小学数学教师的智能试卷生成系统,能够根据不同的学段要求自动生成符合教学大纲的数学题目。 + +### 设计目标 +- 为小学、初中、高中教师提供个性化的试卷生成服务 +- 确保题目难度与学段匹配 +- 避免题目重复,保证每次生成的试卷都是唯一的 +- 提供友好的命令行交互界面 + +## 系统架构 + +### 类图结构 +``` +ExamSystem +├── User +├── QuestionGenerator +│ ├── PrimarySchoolGenerator +│ ├── MiddleSchoolGenerator +│ └── HighSchoolGenerator +└── 文件管理模块 +``` + +### 主要类说明 + +#### 1. User类 +- 属性:username, password, user_type +- 功能:存储用户信息和权限 + +#### 2. QuestionGenerator基类 +- 抽象方法:generate_question() +- 通用方法:生成操作数、运算符 + +#### 3. 各学段生成器 +- PrimarySchoolGenerator:小学题目生成 +- MiddleSchoolGenerator:初中题目生成 +- HighSchoolGenerator:高中题目生成 + +#### 4. ExamSystem主类 +- 用户管理:登录验证、会话管理 +- 题目生成:协调各生成器工作 +- 文件操作:试卷保存、题目去重 + +## 功能详解 + +### 1. 用户登录验证 +```python +def login(self) -> bool: + # 实现用户名密码验证 + # 支持9个预设账号 + # 提供友好的错误提示 +``` + +### 2. 题目生成算法 + +#### 小学题目 +- 操作数:2-3个随机数(1-100) +- 运算符:仅使用 + 和 - +- 示例:"23 + 45 - 12" + +#### 初中题目 +- 操作数:3-4个随机数(1-100) +- 运算符:+、-、*、/ +- 示例:"15 * 3 + 28 / 4" + +#### 高中题目 +- 操作数:4-5个随机数(1-100) +- 运算符:+、-、*、/ +- 括号:30%概率包含括号运算 +- 示例:"(15 + 3) * 2 - 8 / 4" + +### 3. 题目去重机制 +```python +def load_existing_questions(self, username: str) -> Set[str]: + # 加载用户所有历史题目 + # 使用集合实现快速查找 + +def generate_unique_question(self, generator, existing_questions): + # 生成题目并检查是否重复 + # 最大尝试次数100次 +``` + +### 4. 文件存储系统 +- 目录结构:papers/用户名/ +- 文件名:年-月-日-时-分-秒.txt +- 格式:题号 + 题目 + 空行 + +## 使用指南 + +### 快速开始 +1. 确保安装Python 3.6+ +2. 下载项目代码 +3. 运行命令:`python src/main.py` + +### 交互命令说明 + +#### 登录阶段 +``` +请输入用户名和密码(用空格隔开):primary_teacher1 primary123 +当前选择为 小学 出题 +``` + +#### 生成试卷 +``` +准备生成 小学 数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录): +>>> 15 +试卷已生成:papers/primary_teacher1/2025-01-15-14-30-25.txt +``` + +#### 切换学段 +``` +>>> 切换为 初中 +已切换为 初中 出题 +准备生成 初中 数学题目,请输入生成题目数量: +>>> 20 +``` + +#### 退出操作 +``` +>>> -1 +退出当前用户 +``` + +## 技术实现 + +### 核心算法 + +#### 1. 表达式生成算法 +```python +def generate_question(self) -> str: + num_operands = random.randint(2, 3) + operands = self.generate_operands(num_operands) + operators = [random.choice(['+', '-']) for _ in range(num_operands - 1)] + + # 构建表达式 + expression = str(operands[0]) + for i in range(num_operands - 1): + expression += f" {operators[i]} {operands[i + 1]}" + + return expression +``` + +#### 2. 括号插入算法(高中) +```python +def _generate_with_brackets(self, operands, operators): + if len(operands) <= 3 or random.random() < 0.3: + # 简单表达式或不加括号 + return simple_expression + + # 随机选择插入括号的位置 + bracket_pos = random.randint(0, len(operands) - 3) + # 构建带括号的表达式 +``` + +### 错误处理 +- 输入验证:检查用户名密码格式 +- 数量验证:题目数量必须在10-30之间 +- 类型验证:切换学段时检查有效性 +- 文件操作:异常处理和重试机制 + +### 性能优化 +- 使用集合进行快速去重查找 +- 限制最大尝试次数防止无限循环 +- 惰性加载历史题目 + +## 测试用例 + +### 功能测试 + +#### 1. 登录测试 +```python +# 测试正确登录 +assert login("primary_teacher1", "primary123") == True + +# 测试错误密码 +assert login("primary_teacher1", "wrong") == False + +# 测试不存在的用户 +assert login("nonexistent", "123") == False +``` + +#### 2. 题目生成测试 +```python +# 测试小学题目 +question = PrimarySchoolGenerator().generate_question() +assert question.count('+') + question.count('-') >= 1 +assert '*' not in question and '/' not in question + +# 测试题目去重 +existing = {"1 + 2"} +new_question = generate_unique_question(generator, existing) +assert new_question != "1 + 2" +``` + +#### 3. 文件操作测试 +```python +# 测试文件保存 +filename = save_exam_paper(["1 + 1", "2 * 2"]) +assert os.path.exists(filename) + +# 测试文件格式 +content = read_file(filename) +assert "1. 1 + 1" in content +assert "\n\n2. 2 * 2" in content +``` + +## 部署说明 + +### 环境要求 +- Python 3.6或更高版本 +- 磁盘空间:至少10MB用于存储试卷文件 +- 内存:最少128MB RAM + +### 安装步骤 +1. 下载项目代码 +2. 无需安装依赖(使用标准库) +3. 直接运行:`python src/main.py` + +### 文件结构部署 +``` +项目根目录/ +├── src/ # 源代码 +│ └── main.py # 主程序 +├── doc/ # 文档 +│ └── README.md # 详细说明 +├── papers/ # 试卷存储(自动创建) +└── README.md # 项目简介 +``` + +### 维护说明 +- 定期清理papers目录下的旧试卷文件 +- 如需修改预设账号,编辑main.py中的users列表 +- 支持自定义题目生成规则 + +## 扩展性 + +### 可扩展功能 +1. **题目难度调节**:增加难度级别参数 +2. **题目类型扩展**:支持几何、代数等不同类型 +3. **批量生成**:支持一次生成多份试卷 +4. **Web界面**:开发图形化操作界面 +5. **题目导出**:支持PDF、Word格式导出 + +### 接口设计 +```python +# 可扩展的题目生成器接口 +class QuestionGenerator: + def set_difficulty(self, level: int): + """设置题目难度""" + + def generate_with_topics(self, topics: List[str]): + """按主题生成题目""" +``` + +--- + +*最后更新:2025年1月* +*作者:数学教育软件开发组* \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6b59a4b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +# 项目依赖 +# 这个项目使用纯Python标准库,无需额外依赖 \ No newline at end of file diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..62293d8 --- /dev/null +++ b/src/main.py @@ -0,0 +1,397 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +中小学数学卷子自动生成程序 +""" + +import os +import sys +import re +import random +import time +from datetime import datetime +from typing import List, Dict, Tuple, Set + + +class User: + """用户类""" + def __init__(self, username: str, password: str, user_type: str): + self.username = username + self.password = password + self.user_type = user_type + + +class QuestionGenerator: + """题目生成器基类""" + + def __init__(self): + self.operators = ['+', '-', '*', '/'] + + def generate_question(self) -> str: + """生成一道题目""" + raise NotImplementedError("子类必须实现此方法") + + def generate_operands(self, count: int) -> List[int]: + """生成操作数""" + return [random.randint(1, 100) for _ in range(count)] + + def generate_operator(self) -> str: + """生成运算符""" + return random.choice(self.operators) + + +class PrimarySchoolGenerator(QuestionGenerator): + """小学题目生成器""" + + def generate_question(self) -> str: + num_operands = random.randint(2, 3) # 小学2-3个操作数 + operands = self.generate_operands(num_operands) + + # 小学使用+,-,*,/和() + operators = [random.choice(['+', '-', '*', '/']) for _ in range(num_operands - 1)] + + # 30%概率添加括号 + if random.random() < 0.3 and num_operands >= 3: + question = self._generate_with_brackets(operands, operators) + else: + question = str(operands[0]) + for i in range(num_operands - 1): + question += f" {operators[i]} {operands[i + 1]}" + + return question + + def _generate_with_brackets(self, operands: List[int], operators: List[str]) -> str: + """生成带括号的表达式""" + bracket_pos = random.randint(0, len(operands) - 2) + question = "" + + for i in range(len(operands)): + if i == bracket_pos: + question += "(" + + question += str(operands[i]) + + if i == bracket_pos + 1: + question += ")" + + if i < len(operators): + question += f" {operators[i]}" + + return question + + +class MiddleSchoolGenerator(QuestionGenerator): + """初中题目生成器""" + + def generate_question(self) -> str: + num_operands = random.randint(3, 4) # 初中3-4个操作数 + operands = self.generate_operands(num_operands) + operators = [self.generate_operator() for _ in range(num_operands - 1)] + + # 确保至少有一个平方或开根号运算符 + question_parts = [] + has_special_operator = False + + for i in range(num_operands): + # 50%概率对操作数进行特殊运算 + if not has_special_operator and random.random() < 0.5: + if random.choice([True, False]): + # 平方 + question_parts.append(f"{operands[i]}²") + else: + # 开根号 + question_parts.append(f"√{operands[i]}") + has_special_operator = True + else: + question_parts.append(str(operands[i])) + + if i < len(operators): + question_parts.append(operators[i]) + + # 如果没有特殊运算符,强制添加一个 + if not has_special_operator: + pos = random.randint(0, num_operands - 1) + if random.choice([True, False]): + question_parts[pos * 2] = f"{operands[pos]}²" + else: + question_parts[pos * 2] = f"√{operands[pos]}" + + return " ".join(question_parts) + + +class HighSchoolGenerator(QuestionGenerator): + """高中题目生成器""" + + def __init__(self): + super().__init__() + self.trig_functions = ['sin', 'cos', 'tan'] + + def generate_question(self) -> str: + num_operands = random.randint(4, 5) # 高中4-5个操作数 + operands = self.generate_operands(num_operands) + operators = [self.generate_operator() for _ in range(num_operands - 1)] + + # 确保至少有一个三角函数 + question_parts = [] + has_trig_function = False + + for i in range(num_operands): + # 50%概率对操作数进行三角函数运算 + if not has_trig_function and random.random() < 0.5: + trig_func = random.choice(self.trig_functions) + angle = random.randint(1, 90) # 合理的角度范围 + question_parts.append(f"{trig_func}({angle}°)") + has_trig_function = True + else: + question_parts.append(str(operands[i])) + + if i < len(operators): + question_parts.append(operators[i]) + + # 如果没有三角函数,强制添加一个 + if not has_trig_function: + pos = random.randint(0, num_operands - 1) + trig_func = random.choice(self.trig_functions) + angle = random.randint(1, 90) + question_parts[pos * 2] = f"{trig_func}({angle}°)" + + # 高中题目可以包含括号 + question = self._generate_with_brackets(question_parts) + return question + + def _generate_with_brackets(self, question_parts: List[str]) -> str: + """生成带括号的表达式""" + # 简单情况下直接返回 + if len(question_parts) <= 5 or random.random() < 0.3: + return " ".join(question_parts) + + # 添加括号 + bracket_pos = random.randint(0, (len(question_parts) - 3) // 2) * 2 + result_parts = [] + + for i, part in enumerate(question_parts): + if i == bracket_pos: + result_parts.append("(") + + result_parts.append(part) + + if i == bracket_pos + 2: + result_parts.append(")") + + return " ".join(result_parts) + + +class ExamSystem: + """考试系统主类""" + + def __init__(self): + # 预设用户账户(根据附表-1) + self.users = [ + User("张三1", "123", "小学"), + User("张三2", "123", "小学"), + User("张三3", "123", "小学"), + User("李四1", "123", "初中"), + User("李四2", "123", "初中"), + User("李四3", "123", "初中"), + User("王五1", "123", "高中"), + User("王五2", "123", "高中"), + User("王五3", "123", "高中") + ] + + self.current_user = None + self.current_type = None + self.generated_questions = set() # 存储已生成的题目用于去重 + + # 创建用户文件夹 + self.create_user_folders() + + def create_user_folders(self): + """创建用户文件夹""" + # 获取项目根目录的绝对路径 + project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + for user in self.users: + folder_path = os.path.join(project_root, "papers", user.username) + os.makedirs(folder_path, exist_ok=True) + + def login(self) -> bool: + """用户登录""" + while True: + try: + input_str = input("请输入用户名和密码(用空格隔开):") + if input_str.strip().lower() == 'exit': + return False + + parts = input_str.split() + if len(parts) != 2: + print("请输入正确的用户名和密码格式!") + continue + + username, password = parts + + for user in self.users: + if user.username == username and user.password == password: + self.current_user = user + self.current_type = user.user_type + print(f"当前选择为 {self.current_type} 出题") + return True + + print("请输入正确的用户名、密码") + + except KeyboardInterrupt: + print("\n程序已退出") + return False + except Exception as e: + print(f"输入错误:{e}") + + def switch_type(self, new_type: str): + """切换题目类型""" + if new_type in ["小学", "初中", "高中"]: + self.current_type = new_type + print(f"已切换为 {new_type} 出题") + else: + print("请输入小学、初中和高中三个选项中的一个") + + def get_generator(self) -> QuestionGenerator: + """获取对应的题目生成器""" + if self.current_type == "小学": + return PrimarySchoolGenerator() + elif self.current_type == "初中": + return MiddleSchoolGenerator() + elif self.current_type == "高中": + return HighSchoolGenerator() + else: + raise ValueError("无效的题目类型") + + def load_existing_questions(self, username: str) -> Set[str]: + """加载该用户已生成的所有题目""" + existing_questions = set() + # 获取项目根目录的绝对路径 + project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + user_folder = os.path.join(project_root, "papers", username) + + if os.path.exists(user_folder): + for filename in os.listdir(user_folder): + if filename.endswith('.txt'): + filepath = os.path.join(user_folder, filename) + try: + with open(filepath, 'r', encoding='utf-8') as f: + content = f.read() + # 提取所有题目(忽略题号和空行) + questions = re.findall(r'\d+\.\s*(.+?)(?=\n\n|$)', content, re.DOTALL) + existing_questions.update(questions) + except: + continue + + return existing_questions + + def generate_unique_question(self, generator: QuestionGenerator, existing_questions: Set[str]) -> str: + """生成唯一的题目""" + max_attempts = 100 + for _ in range(max_attempts): + question = generator.generate_question() + if question not in existing_questions: + existing_questions.add(question) + return question + + raise Exception("无法生成唯一题目,请尝试减少题目数量") + + def generate_exam_paper(self, num_questions: int): + """生成试卷""" + if not 10 <= num_questions <= 30: + print("题目数量必须在10-30之间") + return + + # 加载该用户已存在的题目 + existing_questions = self.load_existing_questions(self.current_user.username) + + generator = self.get_generator() + questions = [] + + try: + for i in range(num_questions): + question = self.generate_unique_question(generator, existing_questions) + questions.append(question) + except Exception as e: + print(f"生成题目时出错:{e}") + return + + # 生成文件名 + timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M-%S") + filename = f"{timestamp}.txt" + # 获取项目根目录的绝对路径 + project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + user_folder = os.path.join(project_root, "papers", self.current_user.username) + filepath = os.path.join(user_folder, filename) + + # 写入文件 + try: + with open(filepath, 'w', encoding='utf-8') as f: + for i, question in enumerate(questions, 1): + f.write(f"{i}. {question}\n\n") + print(f"试卷已生成:{filepath}") + except Exception as e: + print(f"保存文件时出错:{e}") + + def run(self): + """运行主程序""" + print("=== 中小学数学卷子自动生成程序 ===") + print("输入'exit'可以退出程序") + + while True: + # 登录 + if not self.login(): + break + + # 登录后的主循环 + while self.current_user: + try: + print(f"\n准备生成 {self.current_type} 数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):") + + user_input = input(">>> ").strip() + + if user_input.lower() == 'exit': + print("程序已退出") + return + + # 检查切换命令 + if user_input.startswith("切换为"): + parts = user_input.split("切换为") + if len(parts) == 2: + new_type = parts[1].strip() + self.switch_type(new_type) + continue + + # 处理题目数量输入 + if user_input == "-1": + print("退出当前用户") + self.current_user = None + break + + try: + num_questions = int(user_input) + if num_questions == -1: + print("退出当前用户") + self.current_user = None + break + + self.generate_exam_paper(num_questions) + + except ValueError: + print("请输入有效的数字(10-30)或-1退出") + + except KeyboardInterrupt: + print("\n程序已退出") + return + except Exception as e: + print(f"发生错误:{e}") + + +def main(): + """主函数""" + system = ExamSystem() + system.run() + + +if __name__ == "__main__": + main() \ No newline at end of file -- 2.34.1 From 60201877be3aa906b06222ea158e4aa1883c93a4 Mon Sep 17 00:00:00 2001 From: xuan <1505557421@qq.com> Date: Thu, 25 Sep 2025 19:10:57 +0800 Subject: [PATCH 2/3] =?UTF-8?q?Add:=20=E5=AE=8C=E5=96=84=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/doc/README.md b/doc/README.md index c282131..5f54ef1 100644 --- a/doc/README.md +++ b/doc/README.md @@ -265,5 +265,3 @@ class QuestionGenerator: --- -*最后更新:2025年1月* -*作者:数学教育软件开发组* \ No newline at end of file -- 2.34.1 From 8260bd1f988ac31f0ad8223627b97d7c4c1508f7 Mon Sep 17 00:00:00 2001 From: xuan <1505557421@qq.com> Date: Thu, 25 Sep 2025 19:20:18 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dbug=EF=BC=8C=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.py | 207 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 158 insertions(+), 49 deletions(-) diff --git a/src/main.py b/src/main.py index 62293d8..cbf670c 100644 --- a/src/main.py +++ b/src/main.py @@ -6,17 +6,24 @@ """ import os -import sys -import re import random -import time +import re from datetime import datetime -from typing import List, Dict, Tuple, Set +from typing import List, Set, Optional class User: """用户类""" - def __init__(self, username: str, password: str, user_type: str): + + def __init__(self, username: str, password: str, user_type: str) -> None: + """ + 初始化用户 + + Args: + username: 用户名 + password: 密码 + user_type: 用户类型(小学/初中/高中) + """ self.username = username self.password = password self.user_type = user_type @@ -25,7 +32,8 @@ class User: class QuestionGenerator: """题目生成器基类""" - def __init__(self): + def __init__(self) -> None: + """初始化题目生成器""" self.operators = ['+', '-', '*', '/'] def generate_question(self) -> str: @@ -33,7 +41,15 @@ class QuestionGenerator: raise NotImplementedError("子类必须实现此方法") def generate_operands(self, count: int) -> List[int]: - """生成操作数""" + """ + 生成操作数 + + Args: + count: 操作数数量 + + Returns: + 操作数列表 + """ return [random.randint(1, 100) for _ in range(count)] def generate_operator(self) -> str: @@ -44,15 +60,25 @@ class QuestionGenerator: class PrimarySchoolGenerator(QuestionGenerator): """小学题目生成器""" + def __init__(self) -> None: + """初始化小学题目生成器""" + super().__init__() + self.bracket_probability = 0.3 # 括号概率 + self.min_operands = 2 + self.max_operands = 5 + def generate_question(self) -> str: - num_operands = random.randint(2, 3) # 小学2-3个操作数 + """生成小学题目""" + num_operands = random.randint(self.min_operands, self.max_operands) operands = self.generate_operands(num_operands) # 小学使用+,-,*,/和() - operators = [random.choice(['+', '-', '*', '/']) for _ in range(num_operands - 1)] + operators = [random.choice(['+', '-', '*', '/']) + for _ in range(num_operands - 1)] - # 30%概率添加括号 - if random.random() < 0.3 and num_operands >= 3: + # 根据概率添加括号 + if (random.random() < self.bracket_probability and + num_operands >= 3): question = self._generate_with_brackets(operands, operators) else: question = str(operands[0]) @@ -61,8 +87,18 @@ class PrimarySchoolGenerator(QuestionGenerator): return question - def _generate_with_brackets(self, operands: List[int], operators: List[str]) -> str: - """生成带括号的表达式""" + def _generate_with_brackets(self, operands: List[int], + operators: List[str]) -> str: + """ + 生成带括号的表达式 + + Args: + operands: 操作数列表 + operators: 运算符列表 + + Returns: + 带括号的表达式字符串 + """ bracket_pos = random.randint(0, len(operands) - 2) question = "" @@ -84,8 +120,16 @@ class PrimarySchoolGenerator(QuestionGenerator): class MiddleSchoolGenerator(QuestionGenerator): """初中题目生成器""" + def __init__(self) -> None: + """初始化初中题目生成器""" + super().__init__() + self.min_operands = 2 + self.max_operands = 5 + self.special_operator_probability = 0.5 + def generate_question(self) -> str: - num_operands = random.randint(3, 4) # 初中3-4个操作数 + """生成初中题目""" + num_operands = random.randint(self.min_operands, self.max_operands) operands = self.generate_operands(num_operands) operators = [self.generate_operator() for _ in range(num_operands - 1)] @@ -94,8 +138,9 @@ class MiddleSchoolGenerator(QuestionGenerator): has_special_operator = False for i in range(num_operands): - # 50%概率对操作数进行特殊运算 - if not has_special_operator and random.random() < 0.5: + # 根据概率对操作数进行特殊运算 + if (not has_special_operator and + random.random() < self.special_operator_probability): if random.choice([True, False]): # 平方 question_parts.append(f"{operands[i]}²") @@ -123,12 +168,19 @@ class MiddleSchoolGenerator(QuestionGenerator): class HighSchoolGenerator(QuestionGenerator): """高中题目生成器""" - def __init__(self): + def __init__(self) -> None: + """初始化高中题目生成器""" super().__init__() self.trig_functions = ['sin', 'cos', 'tan'] + self.min_operands = 2 + self.max_operands = 5 + self.trig_probability = 0.5 + self.bracket_probability = 0.3 + self.max_angle = 90 def generate_question(self) -> str: - num_operands = random.randint(4, 5) # 高中4-5个操作数 + """生成高中题目""" + num_operands = random.randint(self.min_operands, self.max_operands) operands = self.generate_operands(num_operands) operators = [self.generate_operator() for _ in range(num_operands - 1)] @@ -137,10 +189,11 @@ class HighSchoolGenerator(QuestionGenerator): has_trig_function = False for i in range(num_operands): - # 50%概率对操作数进行三角函数运算 - if not has_trig_function and random.random() < 0.5: + # 根据概率对操作数进行三角函数运算 + if (not has_trig_function and + random.random() < self.trig_probability): trig_func = random.choice(self.trig_functions) - angle = random.randint(1, 90) # 合理的角度范围 + angle = random.randint(1, self.max_angle) # 合理的角度范围 question_parts.append(f"{trig_func}({angle}°)") has_trig_function = True else: @@ -153,7 +206,7 @@ class HighSchoolGenerator(QuestionGenerator): if not has_trig_function: pos = random.randint(0, num_operands - 1) trig_func = random.choice(self.trig_functions) - angle = random.randint(1, 90) + angle = random.randint(1, self.max_angle) question_parts[pos * 2] = f"{trig_func}({angle}°)" # 高中题目可以包含括号 @@ -161,9 +214,17 @@ class HighSchoolGenerator(QuestionGenerator): return question def _generate_with_brackets(self, question_parts: List[str]) -> str: - """生成带括号的表达式""" + """ + 生成带括号的表达式 + + Args: + question_parts: 题目部分列表 + + Returns: + 带括号的表达式字符串 + """ # 简单情况下直接返回 - if len(question_parts) <= 5 or random.random() < 0.3: + if len(question_parts) <= 5 or random.random() < self.bracket_probability: return " ".join(question_parts) # 添加括号 @@ -185,7 +246,8 @@ class HighSchoolGenerator(QuestionGenerator): class ExamSystem: """考试系统主类""" - def __init__(self): + def __init__(self) -> None: + """初始化考试系统""" # 预设用户账户(根据附表-1) self.users = [ User("张三1", "123", "小学"), @@ -199,14 +261,14 @@ class ExamSystem: User("王五3", "123", "高中") ] - self.current_user = None - self.current_type = None - self.generated_questions = set() # 存储已生成的题目用于去重 + self.current_user: Optional[User] = None + self.current_type: Optional[str] = None + self.generated_questions: Set[str] = set() # 存储已生成的题目用于去重 # 创建用户文件夹 self.create_user_folders() - def create_user_folders(self): + def create_user_folders(self) -> None: """创建用户文件夹""" # 获取项目根目录的绝对路径 project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -244,8 +306,13 @@ class ExamSystem: except Exception as e: print(f"输入错误:{e}") - def switch_type(self, new_type: str): - """切换题目类型""" + def switch_type(self, new_type: str) -> None: + """ + 切换题目类型 + + Args: + new_type: 新的题目类型 + """ if new_type in ["小学", "初中", "高中"]: self.current_type = new_type print(f"已切换为 {new_type} 出题") @@ -253,7 +320,15 @@ class ExamSystem: print("请输入小学、初中和高中三个选项中的一个") def get_generator(self) -> QuestionGenerator: - """获取对应的题目生成器""" + """ + 获取对应的题目生成器 + + Returns: + 题目生成器实例 + + Raises: + ValueError: 无效的题目类型 + """ if self.current_type == "小学": return PrimarySchoolGenerator() elif self.current_type == "初中": @@ -264,8 +339,16 @@ class ExamSystem: raise ValueError("无效的题目类型") def load_existing_questions(self, username: str) -> Set[str]: - """加载该用户已生成的所有题目""" - existing_questions = set() + """ + 加载该用户已生成的所有题目 + + Args: + username: 用户名 + + Returns: + 已存在的题目集合 + """ + existing_questions: Set[str] = set() # 获取项目根目录的绝对路径 project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) user_folder = os.path.join(project_root, "papers", username) @@ -278,15 +361,29 @@ class ExamSystem: with open(filepath, 'r', encoding='utf-8') as f: content = f.read() # 提取所有题目(忽略题号和空行) - questions = re.findall(r'\d+\.\s*(.+?)(?=\n\n|$)', content, re.DOTALL) + questions = re.findall(r'\d+\.\s*(.+?)(?=\n\n|$)', + content, re.DOTALL) existing_questions.update(questions) - except: + except Exception: continue return existing_questions - def generate_unique_question(self, generator: QuestionGenerator, existing_questions: Set[str]) -> str: - """生成唯一的题目""" + def generate_unique_question(self, generator: QuestionGenerator, + existing_questions: Set[str]) -> str: + """ + 生成唯一的题目 + + Args: + generator: 题目生成器 + existing_questions: 已存在的题目集合 + + Returns: + 唯一的题目字符串 + + Raises: + Exception: 无法生成唯一题目 + """ max_attempts = 100 for _ in range(max_attempts): question = generator.generate_question() @@ -296,21 +393,31 @@ class ExamSystem: raise Exception("无法生成唯一题目,请尝试减少题目数量") - def generate_exam_paper(self, num_questions: int): - """生成试卷""" - if not 10 <= num_questions <= 30: - print("题目数量必须在10-30之间") + def generate_exam_paper(self, num_questions: int) -> None: + """ + 生成试卷 + + Args: + num_questions: 题目数量 + """ + min_questions = 10 + max_questions = 30 + + if not min_questions <= num_questions <= max_questions: + print(f"题目数量必须在{min_questions}-{max_questions}之间") return # 加载该用户已存在的题目 - existing_questions = self.load_existing_questions(self.current_user.username) + existing_questions = self.load_existing_questions( + self.current_user.username) generator = self.get_generator() - questions = [] + questions: List[str] = [] try: for i in range(num_questions): - question = self.generate_unique_question(generator, existing_questions) + question = self.generate_unique_question(generator, + existing_questions) questions.append(question) except Exception as e: print(f"生成题目时出错:{e}") @@ -321,7 +428,8 @@ class ExamSystem: filename = f"{timestamp}.txt" # 获取项目根目录的绝对路径 project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - user_folder = os.path.join(project_root, "papers", self.current_user.username) + user_folder = os.path.join(project_root, "papers", + self.current_user.username) filepath = os.path.join(user_folder, filename) # 写入文件 @@ -333,7 +441,7 @@ class ExamSystem: except Exception as e: print(f"保存文件时出错:{e}") - def run(self): + def run(self) -> None: """运行主程序""" print("=== 中小学数学卷子自动生成程序 ===") print("输入'exit'可以退出程序") @@ -346,7 +454,8 @@ class ExamSystem: # 登录后的主循环 while self.current_user: try: - print(f"\n准备生成 {self.current_type} 数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):") + print(f"\n准备生成 {self.current_type} 数学题目," + f"请输入生成题目数量(输入-1将退出当前用户,重新登录):") user_input = input(">>> ").strip() @@ -387,7 +496,7 @@ class ExamSystem: print(f"发生错误:{e}") -def main(): +def main() -> None: """主函数""" system = ExamSystem() system.run() -- 2.34.1