diff --git a/.idea/artifacts/MathPuzzle_jar.xml b/.idea/artifacts/MathPuzzle_jar.xml new file mode 100644 index 0000000..dd95498 --- /dev/null +++ b/.idea/artifacts/MathPuzzle_jar.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/out/artifacts/MathPuzzle_jar + + + + + \ No newline at end of file diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml new file mode 100644 index 0000000..3a2b1e3 --- /dev/null +++ b/.idea/checkstyle-idea.xml @@ -0,0 +1,18 @@ + + + + 10.26.1 + JavaOnly + true + + + + \ No newline at end of file diff --git a/.idea/checkstyleidea-libs/readme.txt b/.idea/checkstyleidea-libs/readme.txt new file mode 100644 index 0000000..579f816 --- /dev/null +++ b/.idea/checkstyleidea-libs/readme.txt @@ -0,0 +1,6 @@ +This folder contains libraries copied from the "MathPuzzle" project. +It is managed by the CheckStyle-IDEA IDE plugin. +Do not modify this folder while the IDE is running. +When the IDE is stopped, you may delete this folder at any time. It will be recreated as needed. +In order to prevent the CheckStyle-IDEA IDE plugin from creating this folder, +uncheck the "Copy libraries from project directory" option in the CheckStyle-IDEA settings dialog. diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..11b084a --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,604 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..b9d18bf --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index f64831b..07115cd 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,8 +1,5 @@ - - diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..2b63946 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MathPuzzle.iml b/MathPuzzle.iml index c90834f..9ef7693 100644 --- a/MathPuzzle.iml +++ b/MathPuzzle.iml @@ -1,5 +1,8 @@ + + diff --git a/README.md b/README.md deleted file mode 100644 index 4abb17d..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# AutoGeneratePuzzle - diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..d081805 --- /dev/null +++ b/doc/README.md @@ -0,0 +1,153 @@ +# 数学题目生成系统项目说明文档 + +## 1. 项目概述 + +### 1.1 项目名称 +中小学数学卷子自动生成程序 (Math Puzzle System) + +### 1.2 项目简介 +数学题目生成系统是一个基于Java开发的教育类应用程序,专门用于生成不同教育级别的数学题目。系统支持小学、初中和高中三个级别的题目生成,并提供用户管理、题目去重、试卷保存等功能。 + +### 1.3 项目目标 +- 为不同教育级别的学生提供定制化的数学练习题目 +- 避免生成重复题目,提高学习效率 +- 提供用户管理功能,支持多用户使用 +- 生成的题目可保存为文件,便于打印和复习 + +## 2. 功能特性 + +### 2.1 用户管理 +- **用户注册/登录**:支持现有用户登录系统 +- **用户级别切换**:支持在小学、初中、高中三个级别间动态切换 +- **个性化存储**:为每个用户创建独立的目录存储历史题目 + +### 2.2 题目生成 +- **小学级别**:生成包含四则运算和括号的简单数学题目,且保证运算结果不会出现负数 +- **初中级别**:生成包含四则运算、平方、开根号的数学题目 +- **高中级别**:生成包含四则运算和三角函数(sin、cos、tan)的数学题目 + +### 2.3 题目去重 +- **历史题目检查**:自动检查用户历史题目,避免生成与前述txt文件中的重复题目 +- **会话内去重**:防止同一次生成过程中出现重复题目 + +### 2.4 文件管理 +- **自动目录创建**:为每个用户创建专属目录 +- **时间戳命名**:生成的试卷以时间戳命名,确保唯一性 +- **格式化输出**:题目按照标准格式:前有题号,题干之间添加一行间隔,便于阅读 + +## 3. 系统架构 + +### 3.1 核心模块 + +#### 3.1.1 控制器层 (Controller) +- `StartController`:系统主控制器,处理用户交互和系统流程控制 + +#### 3.1.2 服务层 (Service) +- `QuestionGenerator`:题目生成器接口 +- `PrimarySchoolGenerator`:小学题目生成器 +- `JuniorHighGenerator`:初中题目生成器 +- `SeniorHighGenerator`:高中题目生成器 +- `CaculatePrimary`:小学数学表达式计算器(保证结果不出现负数) +- `FileHandler`:文件处理器 +- `QuestionDeduplicator`:题目去重器 + +#### 3.1.3 实体层 (Entity) +- `User`:用户实体类,包含用户名、密码和级别信息 + +### 3.2 交互流程 +1. 用户启动程序 +2. 系统要求用户登录 +3. 用户选择题目数量和级别 +4. 系统生成相应级别的数学题目 +5. 题目去重检查 +6. 生成结果展示并保存到文件 + +## 4. 使用说明 + +### 4.1 启动程序 +```bash +java -jar .\mathpuzzle.jar +``` + +### 4.2 用户操作 +1. **登录**:输入用户名和密码进行登录 +2. **输入题目数量**:输入10-30之间的数字(输入-1退出当前用户) +3. **切换级别**:输入"切换为XX"指令切换到相应级别(如"切换为初中") +4. **查看结果**:系统生成题目并显示,同时保存到用户目录 + +### 4.3 文件结构 +``` +./ +├── [用户名]/ +│ ├── 2024-01-01-12-00-00.txt +│ ├── 2024-01-01-12-05-30.txt +│ └── ... +``` + +## 5. 技术特点 + +### 5.1 设计模式 +- **策略模式**:不同级别的题目生成器实现统一接口 +- **工厂模式**:根据用户级别动态创建相应的题目生成器 + +### 5.2 算法特色 +- **双栈算法**:用于计算小学级别的数学表达式 +- **随机生成算法**:确保题目多样性和随机性 +- **智能去重算法**:避免生成重复题目 + +### 5.3 代码规范 +- 遵循Google Java编码规范 +- 完整的Javadoc文档 +- 清晰的包结构和类设计 + +## 6. 系统要求 + +### 6.1 运行环境 +- Java 8或更高版本 +- 至少50MB可用磁盘空间(用于存储题目文件) + +### 6.2 权限要求 +- 需要文件读写权限(用于创建用户目录和保存题目文件) + +## 7. 扩展性说明 + +### 7.1 可扩展功能 +- 支持更多教育级别的题目生成 +- 添加更多数学运算符和函数 +- 实现题目难度分级 +- 增加图形界面支持 + +### 7.2 代码扩展 +- 通过实现`QuestionGenerator`接口添加新的题目生成器 +- 通过继承或修改现有生成器类调整题目生成规则 +- 扩展用户实体类添加更多用户属性 + +## 8. 项目维护 + +### 8.1 代码维护 +- 遵循单一职责原则,每个类只负责特定功能 +- 使用接口编程,便于功能扩展和替换 +- 完整的异常处理机制 + +### 8.2 版本管理 +- 建议使用Git进行版本控制 +- 遵循语义化版本控制规范 +- 定期备份重要数据文件 + +## 9. 注意事项 + +1. 题目数量限制在10-30道之间,确保生成效率 +2. 系统会自动过滤负数结果,确保题目合理性 +3. 生成的题目文件会自动保存,建议定期清理过期文件 +4. 初中和高中题目包含高级运算符,需要相应的数学知识 +5. 设置循环上限,在无法生成完全不重复的题目时自动退出并给出错误信息 + +## 10. 联系方式 + +如需技术支持或功能定制,请联系项目维护者。 + +--- + +**版本**:1.0 +**最后更新**:2025年 +**文档状态**:正式版 \ No newline at end of file diff --git a/src/META-INF/MANIFEST.MF b/src/META-INF/MANIFEST.MF new file mode 100644 index 0000000..79eb620 --- /dev/null +++ b/src/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: mathpuzzle.Main + diff --git a/src/Main.java b/src/Main.java deleted file mode 100644 index 2d15bb7..0000000 --- a/src/Main.java +++ /dev/null @@ -1,5 +0,0 @@ -public class Main { - public static void main(String[] args) { - System.out.println("Hello World!"); - } -} diff --git a/src/mathpuzzle/Main.java b/src/mathpuzzle/Main.java new file mode 100644 index 0000000..d80e7a1 --- /dev/null +++ b/src/mathpuzzle/Main.java @@ -0,0 +1,29 @@ +package mathpuzzle; + +import mathpuzzle.controller.StartController; + +/** + * 程序主入口类,启动数学题目生成系统。 + * + *

该类包含main方法,作为程序的入口点,负责启动整个数学题目生成系统。 + * 系统启动后会初始化控制器并开始处理用户交互。 + * + * @author 杨博文 + * @version 1.0 + * @since 2025 + */ +public class Main { + + /** + * 程序主入口方法。 + * + *

该方法创建启动控制器实例并调用start方法, + * 启动整个数学题目生成系统的主循环。 + * + * @param args 命令行参数(当前未使用) + */ + public static void main(String[] args) { + StartController startController = new StartController(); + startController.start(); + } +} \ No newline at end of file diff --git a/src/mathpuzzle/controller/StartController.java b/src/mathpuzzle/controller/StartController.java new file mode 100644 index 0000000..757f7d6 --- /dev/null +++ b/src/mathpuzzle/controller/StartController.java @@ -0,0 +1,174 @@ +package mathpuzzle.controller; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; +import mathpuzzle.entity.User; +import mathpuzzle.service.FileHandler; +import mathpuzzle.service.JuniorHighGenerator; +import mathpuzzle.service.PrimarySchoolGenerator; +import mathpuzzle.service.QuestionDeduplicator; +import mathpuzzle.service.QuestionGenerator; +import mathpuzzle.service.SeniorHighGenerator; +import mathpuzzle.system.LogSystem; + +/** + * 启动控制器,负责整个数学题目生成系统的主流程控制。 + * + *

该控制器处理用户登录、题目生成数量输入、题目生成逻辑以及用户级别切换等功能。 + * + * @author 杨博文 + * @version 1.0 + * @since 2025 + */ +public class StartController { + + /** + * 日志系统,用于用户登录和管理。 + */ + private LogSystem logSystem = new LogSystem(); + + /** + * 去重器,用于避免生成重复的数学题目。 + */ + private QuestionDeduplicator deduplicator = new QuestionDeduplicator(); + + /** + * 文件处理器,用于保存生成的题目试卷。 + */ + private FileHandler fileHandler = new FileHandler(); + + /** + * 启动整个数学题目生成系统。 + * + *

该方法实现系统主循环,包括用户登录、题目生成和用户级别切换功能。 + * 系统会持续运行直到程序被终止。 + */ + public void start() { + + logSystem.userHashMapInit(); + Scanner scanner = new Scanner(System.in); + while (true) { + User user = logSystem.login(); + if (user == null) { + continue; + } + while (true) { + System.out.println("准备生成" + user.getLevel() + + "数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):"); + String input = scanner.nextLine(); + try { + if ("-1".equals(input)) { + System.out.println("退出当前用户"); + break; + } + int count = Integer.parseInt(input); + if (count < 10 || count > 30) { + System.out.println("题目数量必须在10-30之间!"); + continue; + } + handleQuestionGeneration(user, count); + } catch (NumberFormatException e) { + handleLevelSwitch(user, input); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + } + + /** + * 处理用户级别切换功能。 + * + *

当用户输入格式为"切换为XX"的指令时,该方法会解析并切换用户的学习级别。 + * 支持的级别包括:小学、初中、高中。 + * + * @param user 当前登录的用户对象 + * @param input 用户输入的字符串 + */ + private void handleLevelSwitch(User user, String input) { + if (input.startsWith("切换为")) { + String newLevel = input.substring(3); + if ("小学".equals(newLevel) || "初中".equals(newLevel) || "高中".equals(newLevel)) { + user.setLevel(newLevel); + } else { + System.out.println("请输入小学、初中和高中三个选项中的一个"); + } + } else { + System.out.println("无效输入。请输入题目数量或'切换为 XX'指令。"); + } + } + + /** + * 根据指定的级别创建对应的题目生成器。 + * + *

该方法根据用户选择的教育级别返回相应的题目生成器实例。 + * + * @param level 教育级别,支持"小学"、"初中"、"高中" + * @return 对应级别的题目生成器实例,如果级别不支持则返回null + */ + private QuestionGenerator createGenerator(String level) { + switch (level) { + case "小学": + return new PrimarySchoolGenerator(); + case "初中": + return new JuniorHighGenerator(); + case "高中": + return new SeniorHighGenerator(); + default: + return null; + } + } + + /** + * 处理题目生成逻辑,包括去重和保存功能。 + * + *

该方法负责生成指定数量的不重复数学题目,使用去重器避免与历史题目重复, + * 并将生成的题目保存到文件中。 + * + * @param user 当前用户对象 + * @param count 需要生成的题目数量 + * @throws IOException 当文件保存过程中发生错误时抛出 + */ + // 处理题目生成,进行去重工作 + private void handleQuestionGeneration(User user, int count) throws IOException { + QuestionGenerator generator = createGenerator(user.getLevel()); + if (generator == null) { + System.out.println("不支持的题目类型: " + user.getLevel()); + return; + } + // 加载该用户所有历史题目用于查重 + deduplicator.loadExistingQuestions(user); + List finalQuestions = new ArrayList<>(); + int generatedCount = 0; + int maxAttempts = 1000; // 防止因题目空间耗尽而无限循环 + int attempts = 0; + while (generatedCount < count && attempts < maxAttempts) { + attempts++; + List tempQuestions = generator.generateQuestions(1); + if (tempQuestions.isEmpty()) { + continue; + } + String newQuestion = tempQuestions.get(0); + String questionForDedup = newQuestion.endsWith(" =") + ? newQuestion.substring(0, newQuestion.length() - 2) : newQuestion; + if (!deduplicator.isDuplicate(questionForDedup)) { + // 题目不重复,加入最终列表和查重集 + finalQuestions.add(newQuestion); + deduplicator.addQuestion(questionForDedup); // 加入本次会话的查重集,防止本次生成重复 + generatedCount++; + } + // 如果重复,则丢弃,循环继续 + } + if (generatedCount < count) { + System.out.println( + "警告:在尝试了 " + maxAttempts + " 次后,仅生成了 " + generatedCount + " 道不重复的题目。"); + } + for (int i = 0; i < finalQuestions.size(); i++) { + System.out.println((i + 1) + ". " + finalQuestions.get(i)); + } + fileHandler.savePaper(user, finalQuestions); + System.out.println("试卷已成功保存!"); + } +} \ No newline at end of file diff --git a/src/mathpuzzle/entity/User.java b/src/mathpuzzle/entity/User.java new file mode 100644 index 0000000..d63b230 --- /dev/null +++ b/src/mathpuzzle/entity/User.java @@ -0,0 +1,72 @@ +package mathpuzzle.entity; + +/** + * 用户实体类,表示系统中的用户信息。 + * + *

该类包含用户的基本信息,如用户名、密码和学习级别。 + * 用户级别可以是小学、初中或高中。 + * + * @author 杨博文 + * @version 1.0 + * @since 2025 + */ +public class User { + + /** 用户名,不可修改。 */ + private final String name; + + /** 用户密码,不可修改。 */ + private final String password; + + /** 用户当前的学习级别,可以修改。 */ + private String level; + + /** + * 构造一个新的用户对象。 + * + * @param name 用户名,不能为空 + * @param password 用户密码,不能为空 + * @param level 用户学习级别,支持"小学"、"初中"、"高中" + */ + public User(String name, String password, String level) { + this.name = name; + this.password = password; + this.level = level; + } + + /** + * 获取用户名。 + * + * @return 用户名 + */ + public String getName() { + return name; + } + + /** + * 获取用户密码。 + * + * @return 用户密码 + */ + public String getPassword() { + return password; + } + + /** + * 获取用户当前的学习级别。 + * + * @return 用户学习级别 + */ + public String getLevel() { + return level; + } + + /** + * 设置用户的学习级别。 + * + * @param newLevel 新的学习级别,支持"小学"、"初中"、"高中" + */ + public void setLevel(String newLevel) { + level = newLevel; + } +} \ No newline at end of file diff --git a/src/mathpuzzle/service/CaculatePrimary.java b/src/mathpuzzle/service/CaculatePrimary.java new file mode 100644 index 0000000..ca7aadc --- /dev/null +++ b/src/mathpuzzle/service/CaculatePrimary.java @@ -0,0 +1,95 @@ +package mathpuzzle.service; + +import static mathpuzzle.service.PrimarySchoolGenerator.isNumeric; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Stack; + +/** + * 小学数学表达式计算器,用于计算小学级别的数学表达式。 + * + *

该类使用双栈算法实现中缀表达式的计算,支持加减乘除四则运算和括号。 + * 运算符优先级:乘除高于加减,括号具有最高优先级。 + * + * @author 杨博文 + * @version 1.0 + * @since 2025 + */ +public class CaculatePrimary { + + /** 数字栈,用于存储操作数。 */ + private final Stack numStack = new Stack<>(); + + /** 操作符栈,用于存储运算符和括号。 */ + private final Stack opStack = new Stack<>(); + + /** 运算符优先级映射表,数字越大优先级越高。 */ + private final Map precedence = new HashMap<>(); + + /** + * 计算给定的数学表达式。 + * + *

该方法使用双栈算法计算表达式的值,支持四则运算和括号。 + * + * @param expression 表达式元素列表,包含数字、运算符和括号 + * @return 表达式的计算结果 + */ + public double caculate(List expression) { + precedence.put("+", 1); + precedence.put("-", 1); + precedence.put("*", 2); + precedence.put("/", 2); + for (String opOrNum : expression) { + if (isNumeric(opOrNum)) { + numStack.push(Double.parseDouble(opOrNum)); + } else if (opOrNum.equals("(")) { + opStack.push(opOrNum); + } else if (opOrNum.equals(")")) { + while (!opStack.peek().equals("(") && !opStack.isEmpty()) { + doCaculte(); + } + opStack.pop(); + } else if (precedence.containsKey(opOrNum)) { + while (!opStack.isEmpty() && !opStack.peek().equals("(") + && precedence.get(opOrNum) <= precedence.get(opStack.peek())) { + doCaculte(); + } + opStack.push(opOrNum); + } + } + while (!opStack.isEmpty()) { + doCaculte(); + } + return numStack.pop(); + } + + /** + * 执行一次基本的二元运算操作。 + * + *

从数字栈中弹出两个操作数,从操作符栈中弹出一个运算符, + * 执行相应的运算,并将结果压入数字栈。 + */ + public void doCaculte() { + String op = opStack.pop(); + double num2 = numStack.pop(); + double num1 = numStack.pop(); + switch (op) { + case "+": + numStack.push(num1 + num2); + break; + case "-": + numStack.push(num1 - num2); + break; + case "*": + numStack.push(num1 * num2); + break; + case "/": + numStack.push(num1 / num2); + break; + default: + break; + } + } +} \ No newline at end of file diff --git a/src/mathpuzzle/service/FileHandler.java b/src/mathpuzzle/service/FileHandler.java new file mode 100644 index 0000000..cd6dabc --- /dev/null +++ b/src/mathpuzzle/service/FileHandler.java @@ -0,0 +1,69 @@ +package mathpuzzle.service; + + +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import mathpuzzle.entity.User; + +/** + * 文件处理器,负责创建用户目录和保存试卷文件。 + * + *

该类提供用户目录管理功能和试卷文件保存功能。 + * 每次生成的试卷会以时间戳命名保存到对应用户的目录中。 + * + * @author 杨博文 + * @version 1.0 + * @since 2025 + */ +public class FileHandler { + + /** + * 确保用户目录存在,如果不存在则创建。 + * + *

该方法检查用户对应的目录是否存在,如果不存在则创建该目录。 + * 用户目录以用户名命名,位于当前工作目录下。 + * + * @param user 需要创建目录的用户对象 + * @throws IOException 当目录创建过程中发生错误时抛出 + */ + public void ensureUserDirectory(User user) throws IOException { + String dirPath = "./" + user.getName(); + Path path = Paths.get(dirPath); + if (!Files.exists(path)) { + Files.createDirectories(path); + } + } + + /** + * 保存试卷到用户目录中。 + * + *

该方法将生成的题目列表保存到文件中,文件名包含时间戳信息, + * 以确保每次生成的试卷都有唯一的文件名。每个题目前添加题号, 题目之间用空行分隔。 + * + * @param user 试卷所属的用户对象 + * @param questions 需要保存的题目列表 + * @throws IOException 当文件写入过程中发生错误时抛出 + */ + public void savePaper(User user, List questions) throws IOException { + ensureUserDirectory(user); + // 生成文件名:年-月-日-时-分-秒.txt + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss"); + String fileName = LocalDateTime.now().format(formatter) + ".txt"; + String filePath = "./" + user.getName() + "/" + fileName; + + try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) { + for (int i = 0; i < questions.size(); i++) { + writer.write((i + 1) + ". " + questions.get(i)); // 添加题号 + writer.newLine(); + writer.newLine(); // 每题之间空一行 + } + } + } +} \ No newline at end of file diff --git a/src/mathpuzzle/service/JuniorHighGenerator.java b/src/mathpuzzle/service/JuniorHighGenerator.java new file mode 100644 index 0000000..56b6c01 --- /dev/null +++ b/src/mathpuzzle/service/JuniorHighGenerator.java @@ -0,0 +1,186 @@ +package mathpuzzle.service; + +import static mathpuzzle.service.PrimarySchoolGenerator.isNumeric; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * 初中题目生成器,负责生成包含平方或开根号运算的初中级别数学题目。 + * + *

该生成器确保每道题目都包含至少一个高级运算符(平方或开根号), + * 题目结构包含基本的四则运算和高级运算的组合。 + * + * @author 杨博文 + * @version 1.0 + * @since 2025 + */ +public class JuniorHighGenerator implements QuestionGenerator { + + /** + * 高级运算符数组,包含"平方"和"开根号"。 + */ + private static final String[] ADVANCED_OPS = {"平方", "开根号"}; + + /** + * 基本运算符数组,包含四则运算符号。 + */ + private static final String[] OPERATORS = {"+", "-", "*", "/"}; + + /** + * 随机数生成器,用于生成随机题目。 + */ + private final Random random = new Random(); + + @Override + public List generateQuestions(int count) { + List questions = new ArrayList<>(); + for (int i = 0; i < count; i++) { + String question = generateSingleQuestion(); + questions.add(question); + } + return questions; + } + + /** + * 生成单个初中级别的数学题目。 + * + *

该方法确保生成的题目包含至少一个高级运算符(平方或开根号), + * 并根据操作数数量采用不同的生成策略。 + * + * @return 生成的数学题目字符串 + */ + private String generateSingleQuestion() { + List parts = new ArrayList<>(); + int operandCount = random.nextInt(5) + 1; + parts = generateBase(operandCount, parts); + // hasAdvancedOp用以检测下面的循环是否加入了高级运算符,如果没有就启动保底 + boolean hasAdvancedOp = false; + if (operandCount == 1) { + if ("平方".equals(ADVANCED_OPS[random.nextInt(ADVANCED_OPS.length)])) { + parts.add("平方"); + } else { + parts.set(0, "开根号" + parts.get(0)); + } + hasAdvancedOp = true; + } else { + // 遍历查找左括号的合理位置 + for (int i = 0; i < parts.size() - 2; i++) { + // 该位置要为操作数且随机添加括号 + if (isNumeric(parts.get(i)) && random.nextBoolean()) { + // 随机数看取出来的是不是开根号运算符 + if ("开根号".equals(ADVANCED_OPS[random.nextInt(ADVANCED_OPS.length)])) { + parts = generateRoot(parts, i); + } else { // 如果不是开根号就是平方运算 + parts = generateSquare(parts, i); + } + hasAdvancedOp = true; + break; + } + } + } + // 启动保底强制加入一个高级运算符 + if (!hasAdvancedOp) { + parts = forceAddAdvancedOp(parts); + } + return String.join(" ", parts) + " ="; + } + + /** + * 生成基本的四则运算表达式部分。 + * + *

该方法生成指定数量的操作数和运算符,构成基础的数学表达式。 + * + * @param operandCount 操作数的数量 + * @param parts 用于存储表达式各部分的列表 + * @return 包含基本运算表达式的列表 + */ + public List generateBase(int operandCount, List parts) { + for (int i = 0; i < operandCount; i++) { + int num = random.nextInt(100) + 1; + parts.add(String.valueOf(num)); + if (i < operandCount - 1) { + parts.add(OPERATORS[random.nextInt(OPERATORS.length)]); + } + } + return parts; + } + + /** + * 强制在表达式中添加一个高级运算符作为保底机制。 + * + *

当随机生成过程中没有添加高级运算符时,使用此方法确保 + * 每道题目都包含至少一个高级运算符。 + * + * @param parts 包含表达式各部分的列表 + * @return 添加了高级运算符的表达式列表 + */ + public List forceAddAdvancedOp(List parts) { + String advancedOp = ADVANCED_OPS[random.nextInt(ADVANCED_OPS.length)]; + if ("平方".equals(advancedOp)) { + parts.add("平方"); + } else { // 开根号 + parts.set(0, "开根号(" + parts.get(0)); + parts.set(parts.size() - 1, parts.get(parts.size() - 1) + ")"); + } + return parts; + } + + /** + * 在指定位置生成开根号运算。 + * + *

该方法在表达式指定位置添加开根号运算,可能只对单个操作数 + * 进行开根号,或者对一段子表达式进行开根号。 + * + * @param parts 包含表达式各部分的列表 + * @param i 开根号运算的起始位置 + * @return 添加了开根号运算的表达式列表 + */ + public List generateRoot(List parts, int i) { + if (random.nextBoolean()) { + parts.set(i, "开根号(" + parts.get(i) + ")"); + } else { + parts.set(i, "开根号(" + parts.get(i)); + // 为避免随机数上限出现0,此处要单独判断一下左括号正好括住倒数第二个操作数的情况 + if (i == parts.size() - 3) { + parts.set(parts.size() - 1, parts.get(parts.size() - 1) + ")"); + } else { + while (true) { + int i2 = random.nextInt(parts.size() - 3 - i) + 2; + if (isNumeric(parts.get(i + i2))) { + parts.set(i + i2, parts.get(i + i2) + ")"); + break; + } + } + } + } + return parts; + } + + /** + * 在指定位置生成平方运算。 + * + *

该方法在表达式指定位置添加平方运算,对一段子表达式进行平方运算。 + * + * @param parts 包含表达式各部分的列表 + * @param i 平方运算的起始位置 + * @return 添加了平方运算的表达式列表 + */ + public List generateSquare(List parts, int i) { + parts.set(i, "(" + parts.get(i)); + // 为避免随机数上限出现0,此处要单独判断一下左括号正好括住倒数第二个操作数的情况 + if (i == parts.size() - 3) { + parts.set(parts.size() - 1, parts.get(parts.size() - 1) + ")"); + } else { + while (true) { + int i2 = random.nextInt(parts.size() - 3 - i) + 2; + if (isNumeric(parts.get(i + i2))) { + parts.set(i + i2, parts.get(i + i2) + ")平方"); + break; + } + } + } + return parts; + } +} \ No newline at end of file diff --git a/src/mathpuzzle/service/PrimarySchoolGenerator.java b/src/mathpuzzle/service/PrimarySchoolGenerator.java new file mode 100644 index 0000000..153e134 --- /dev/null +++ b/src/mathpuzzle/service/PrimarySchoolGenerator.java @@ -0,0 +1,121 @@ +package mathpuzzle.service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * 小学题目生成器,负责生成包含四则运算和括号的小学级别数学题目。 + * + *

该生成器专门用于生成适合小学生的数学题目,题目仅包含加减乘除四则运算 + * 和括号,确保计算结果为非负数。 + * + * @author 杨博文 + * @version 1.0 + * @since 2025 + */ +public class PrimarySchoolGenerator implements QuestionGenerator { + + /** 运算符数组,包含四则运算符号。 */ + private static final String[] OPERATORS = {"+", "-", "*", "/"}; + + /** 随机数生成器,用于生成随机题目。 */ + private final Random random = new Random(); + + @Override + public List generateQuestions(int count) { + List questions = new ArrayList<>(); + for (int i = 0; i < count; i++) { + String question = generateSingleQuestion(); + questions.add(question); + } + return questions; + } + + /** + * 生成单个小级别的数学题目. + * + *

该方法生成包含2-5个操作数的四则运算表达式,可能包含括号, + * 并确保计算结果为非负数。如果计算结果为负数,则重新生成。 + * + * @return 生成的小学数学题目字符串 + */ + private String generateSingleQuestion() { + CaculatePrimary caculatePrimary = new CaculatePrimary(); + int operandCount = random.nextInt(4) + 2; // 2-5个操作数 + List parts = new ArrayList<>(); + while (true) { + // 生成基础操作 + parts = generateBase(operandCount, parts); + // 简单添加括号逻辑:随机加一个括号 + if (operandCount > 2 && random.nextBoolean()) { + // 遍历查找左括号的合理位置 + for (int i = 0; i < parts.size() - 2; i++) { + // 该位置要为操作数且随机添加括号 + if (isNumeric(parts.get(i)) && random.nextBoolean()) { + parts.add(i, "("); + i++; + // 为避免随机数上限出现0,此处要单独判断一下左括号正好括住倒数第二个操作数的情况 + if (i == parts.size() - 3) { + parts.add(")"); + } else { + while (true) { + int i2 = random.nextInt(parts.size() - 3 - i) + 2; + if (isNumeric(parts.get(i + i2))) { + parts.add(i + i2 + 1, ")"); + break; + } + } + } + break; + } + } + } + if (caculatePrimary.caculate(parts) >= 0) { + return String.join(" ", parts) + " ="; + } else { + parts.clear(); + } + } + } + + /** + * 判断给定字符串是否为数字。 + * + *

该方法检查字符串是否可以转换为数字格式。 + * + * @param str 待检查的字符串 + * @return 如果字符串是数字则返回true,否则返回false + */ + public static boolean isNumeric(String str) { + if (str == null || str.isEmpty()) { + return false; + } + try { + Double.parseDouble(str); + return true; + } catch (NumberFormatException e) { + return false; + } + } + + /** + * 生成基本的四则运算表达式部分。 + * + *

该方法生成指定数量的操作数和运算符,构成基础的数学表达式。 + * + * @param operandCount 操作数的数量 + * @param parts 用于存储表达式各部分的列表 + * @return 包含基本运算表达式的列表 + */ + public List generateBase(int operandCount, List parts) { + for (int i = 0; i < operandCount; i++) { + int num = random.nextInt(100) + 1; + parts.add(String.valueOf(num)); + if (i < operandCount - 1) { + parts.add(OPERATORS[random.nextInt(OPERATORS.length)]); + } + } + return parts; + } +} \ No newline at end of file diff --git a/src/mathpuzzle/service/QuestionDeduplicator.java b/src/mathpuzzle/service/QuestionDeduplicator.java new file mode 100644 index 0000000..01b827b --- /dev/null +++ b/src/mathpuzzle/service/QuestionDeduplicator.java @@ -0,0 +1,91 @@ +package mathpuzzle.service; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +import mathpuzzle.entity.User; + +/** + * 题目查重器,负责加载历史题目并检查新题目是否重复。 + * + *

该类维护一个题目集合,用于检测当前生成的题目是否与用户的历史题目重复。 + * 通过加载用户目录下的所有历史试卷文件,提取题目内容进行去重检查。 + * + * @author 杨博文 + * @version 1.0 + * @since 2025 + */ +public class QuestionDeduplicator { + + /** + * 存储用户历史题目的集合。 + */ + private final Set existingQuestions = new HashSet<>(); + + /** + * 加载指定用户的所有历史题目。 + * + *

该方法遍历用户目录下的所有.txt文件,读取并解析题目内容, + * 将历史题目添加到去重集合中。此操作会清空之前的题目记录。 + * + * @param user 需要加载历史题目的用户对象 + */ + public void loadExistingQuestions(User user) { + existingQuestions.clear(); + String userDir = "./" + user.getName(); + File dir = new File(userDir); + + if (!dir.exists()) { + return; // 目录不存在,无历史题目 + } + + File[] files = dir.listFiles((d, name) -> name.endsWith(".txt")); + if (files == null) { + return; + } + + for (File file : files) { + try (BufferedReader br = new BufferedReader(new FileReader(file))) { + String line; + while ((line = br.readLine()) != null) { + // 只加载题目行,忽略题号和空行 + if (line.trim().isEmpty() || line.matches("\\d+\\. .*")) { + String questionContent = line.replaceFirst("\\d+\\. ", "").trim(); + if (!questionContent.isEmpty() && !questionContent.equals("=")) { + existingQuestions.add(questionContent); + } + } + } + } catch (IOException e) { + System.err.println("读取历史文件时出错: " + file.getName()); + } + } + } + + /** + * 检查指定题目是否为重复题目。 + * + *

该方法检查给定的题目是否已经存在于历史题目集合中。 + * + * @param question 待检查的题目内容 + * @return 如果题目重复则返回true,否则返回false + */ + public boolean isDuplicate(String question) { + return existingQuestions.contains(question); + } + + /** + * 将新题目添加到去重集合中。 + * + *

该方法将当前生成的题目添加到去重集合中,用于防止 + * 在同一次生成过程中出现重复题目。 + * + * @param question 需要添加的题目内容 + */ + public void addQuestion(String question) { + existingQuestions.add(question); + } +} \ No newline at end of file diff --git a/src/mathpuzzle/service/QuestionGenerator.java b/src/mathpuzzle/service/QuestionGenerator.java new file mode 100644 index 0000000..ab5c4fe --- /dev/null +++ b/src/mathpuzzle/service/QuestionGenerator.java @@ -0,0 +1,28 @@ +package mathpuzzle.service; + +import java.util.List; + +/** + * 题目生成器接口,定义了题目生成器的标准方法。 + * + *

所有具体的题目生成器都应该实现此接口,提供统一的题目生成功能。 + * 不同级别的题目生成器(如小学、初中、高中)可以根据各自的特点 + * 实现不同的生成算法。 + * + * @author 杨博文 + * @version 1.0 + * @since 2025 + */ +public interface QuestionGenerator { + + /** + * 生成指定数量的数学题目。 + * + *

该方法根据实现类的特定规则生成指定数量的数学题目。 + * 生成的题目应该符合对应教育级别的难度要求。 + * + * @param count 需要生成的题目数量 + * @return 包含生成题目的字符串列表 + */ + List generateQuestions(int count); +} \ No newline at end of file diff --git a/src/mathpuzzle/service/SeniorHighGenerator.java b/src/mathpuzzle/service/SeniorHighGenerator.java new file mode 100644 index 0000000..eb6acbb --- /dev/null +++ b/src/mathpuzzle/service/SeniorHighGenerator.java @@ -0,0 +1,126 @@ +package mathpuzzle.service; + +import static mathpuzzle.service.PrimarySchoolGenerator.isNumeric; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * 高中题目生成器,负责生成包含三角函数运算的高中级别数学题目。 + * + *

该生成器确保每道题目都包含至少一个三角函数运算符(sin、cos或tan), + * 题目结构包含基本的四则运算和三角函数运算的组合。 + * + * @author 杨博文 + * @version 1.0 + * @since 2025 + */ +public class SeniorHighGenerator implements QuestionGenerator { + + /** 三角函数运算符数组,包含"sin"、"cos"和"tan"。 */ + private static final String[] TRIG_FUNCS = {"sin", "cos", "tan"}; + + /** 基本运算符数组,包含四则运算符号。 */ + private static final String[] OPERATORS = {"+", "-", "*", "/"}; + + /** 随机数生成器,用于生成随机题目。 */ + private final Random random = new Random(); + + @Override + public List generateQuestions(int count) { + List questions = new ArrayList<>(); + for (int i = 0; i < count; i++) { + String question = generateSingleQuestion(); + questions.add(question); + } + return questions; + } + + /** + * 生成单个高中级别的数学题目。 + * + *

该方法确保生成的题目包含至少一个三角函数运算符, + * 并根据操作数数量采用不同的生成策略。 + * + * @return 生成的数学题目字符串 + */ + private String generateSingleQuestion() { + List parts = new ArrayList<>(); + int operandCount = random.nextInt(5) + 1; + parts = generateBase(operandCount, parts); + String advancedOp; + if (operandCount == 1) { + advancedOp = TRIG_FUNCS[random.nextInt(TRIG_FUNCS.length)]; + parts.set(0, advancedOp + parts.get(0)); + } else { + // 遍历查找左括号的合理位置 + for (int i = 0; i < parts.size(); i++) { + // 最后一次循环保底生成高中三角函数 + if (i == parts.size() - 1) { + advancedOp = TRIG_FUNCS[random.nextInt(TRIG_FUNCS.length)]; + parts.set(i, advancedOp + parts.get(i)); + } else if (isNumeric(parts.get(i)) && random.nextBoolean()) { // 随机数看是否为操作数且随即进入生成程序 + // 进入随机生成tan\sin\cos的程序 + parts = generateTrig(parts, i); + break; + } + } + } + return String.join(" ", parts) + " ="; + } + + /** + * 生成基本的四则运算表达式部分。 + * + *

该方法生成指定数量的操作数和运算符,构成基础的数学表达式。 + * + * @param operandCount 操作数的数量 + * @param parts 用于存储表达式各部分的列表 + * @return 包含基本运算表达式的列表 + */ + // 产生基本操作 + public List generateBase(int operandCount, List parts) { + for (int i = 0; i < operandCount; i++) { + int num = random.nextInt(100) + 1; + parts.add(String.valueOf(num)); + if (i < operandCount - 1) { + parts.add(OPERATORS[random.nextInt(OPERATORS.length)]); + } + } + return parts; + } + + /** + * 在指定位置生成三角函数运算。 + * + *

该方法在表达式指定位置添加三角函数运算,可能只对单个操作数 + * 进行三角函数运算,或者对一段子表达式进行三角函数运算。 + * + * @param parts 包含表达式各部分的列表 + * @param i 三角函数运算的位置 + * @return 添加了三角函数运算的表达式列表 + */ + // 产生三角函数运算符 + public List generateTrig(List parts, int i) { + String trigOp = TRIG_FUNCS[random.nextInt(TRIG_FUNCS.length)]; + if (random.nextBoolean()) { + parts.set(i, trigOp + parts.get(i)); + } else { + parts.set(i, trigOp + "(" + parts.get(i)); + // 为避免随机数上限出现0,此处要单独判断一下左括号正好括住倒数第二个操作数的情况 + if (i == parts.size() - 3) { + parts.set(parts.size() - 1, parts.get(parts.size() - 1) + ")"); + } else { + while (true) { + int i2 = random.nextInt(parts.size() - 3 - i) + 2; + if (isNumeric(parts.get(i + i2))) { + parts.set(i + i2, parts.get(i + i2) + ")"); + break; + } + } + } + } + return parts; + } +} \ No newline at end of file diff --git a/src/mathpuzzle/system/LogSystem.java b/src/mathpuzzle/system/LogSystem.java new file mode 100644 index 0000000..6918d23 --- /dev/null +++ b/src/mathpuzzle/system/LogSystem.java @@ -0,0 +1,47 @@ +package mathpuzzle.system; +import mathpuzzle.entity.User; +import java.util.HashMap; +import java.util.Scanner; + +public class LogSystem { + private final HashMap userHashMap = new HashMap<>(); + public void userHashMapInit() { + // 小学 + userHashMap.put("张三1", new User("张三 1", "123", "小学")); + userHashMap.put("张三2", new User("张三 2", "123", "小学")); + userHashMap.put("张三3", new User("张三 3", "123", "小学")); + // 初中 + userHashMap.put("李四1", new User("李四 1", "123", "初中")); + userHashMap.put("李四2", new User("李四 2", "123", "初中")); + userHashMap.put("李四3", new User("李四 3", "123", "初中")); + // 高中 + userHashMap.put("王五1", new User("王五 1", "123", "高中")); + userHashMap.put("王五2", new User("王五 2", "123", "高中")); + userHashMap.put("王五3", new User("王五 3", "123", "高中")); + } + + public User login() { + System.out.println("请输入用户名和密码,两者之间用空格隔开"); + while(true) { + Scanner scanner = new Scanner(System.in); + String[] info = scanner.nextLine().split(" "); + if(info.length != 2) { + System.out.println("请输入正确格式"); + } else { + String name = info[0]; + String password = info[1]; + User user = userHashMap.get(name); + if (user == null) { + System.out.println("请输入正确的用户名、密码"); + } + else if (!user.getPassword().equals(password)) { + System.out.println("请输入正确的用户名、密码"); + } + else { + System.out.println("当前选择为" + user.getLevel() + "出题"); + return user; + } + } + } + } +}