diff --git a/doc/说明.md b/doc/说明.md new file mode 100644 index 0000000..be9ff3c --- /dev/null +++ b/doc/说明.md @@ -0,0 +1,29 @@ +# 中小学数学卷子自动生成程序 + +## 项目简介 +本项目是一个面向中小学数学老师的卷子自动生成程序。 + +## 功能特性 +- ✅ **用户登录**:支持预设账号登录验证 +- ✅ **题目生成**:自动生成符合难度的数学题目 +- ✅ **难度切换**:支持小学、初中、高中难度切换 +- ✅ **题目查重**:避免生成重复题目 + +## 注意事项 + - 使用切换功能时无提示,在“准备生成 XX 数学题目,请输入生成题目数量(输入-1 + 将退出当前用户,重新登录):”的阶段切换 + + - 提示请输入小学、初中和高中三个选项中的一个后仍输入“切换为XX” + 正确更改后才可退出 + +## 快速开始 + +### 环境要求 +- Java 8+ +- 至少 100MB 磁盘空间 + +### 安装运行 +```bash +cd src +javac -encoding UTF-8 *.java +java MathTestGenerator \ No newline at end of file diff --git a/src/AbstractMathGenerator.java b/src/AbstractMathGenerator.java new file mode 100644 index 0000000..d0385ed --- /dev/null +++ b/src/AbstractMathGenerator.java @@ -0,0 +1,37 @@ +import java.util.List; + +/** + * 数学题目生成器抽象类 + */ +public abstract class AbstractMathGenerator implements MathGenerator { + protected String currentDifficulty; + protected String currentUser; + + /** + * 验证题目数量范围 + */ + protected boolean validateQuestionCount(int count) { + return count >= 10 && count <= 30 || count == -1; + } + + /** + * 验证难度级别 + */ + protected boolean validateDifficulty(String difficulty) { + return difficulty.equals("小学") || difficulty.equals("初中") || difficulty.equals("高中"); + } + + /** + * 获取当前难度 + */ + public String getCurrentDifficulty() { + return currentDifficulty; + } + + /** + * 获取当前用户 + */ + public String getCurrentUser() { + return currentUser; + } +} \ No newline at end of file diff --git a/src/FileService.java b/src/FileService.java new file mode 100644 index 0000000..6bcf2f3 --- /dev/null +++ b/src/FileService.java @@ -0,0 +1,86 @@ +import java.io.*; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +/** + * 文件操作服务 + */ +public class FileService { + private final String baseDir; + + public FileService() { + this.baseDir = System.getProperty("user.dir") + File.separator + "papers"; + initializeStorage(); + } + + /** + * 初始化存储目录 + */ + private void initializeStorage() { + File dir = new File(baseDir); + if (!dir.exists()) { + dir.mkdirs(); + } + } + + /** + * 保存题目到文件 + */ + public void saveQuestions(String username, List questions) { + String timestamp = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()); + String filename = timestamp + ".txt"; + String userDirPath = baseDir + File.separator + username; + + File userDir = new File(userDirPath); + if (!userDir.exists()) { + userDir.mkdirs(); + } + + File file = new File(userDir, filename); + try (PrintWriter writer = new PrintWriter(new FileWriter(file, true))) { + for (int i = 0; i < questions.size(); i++) { + writer.println((i + 1) + ". " + questions.get(i)); + if (i < questions.size() - 1) { + writer.println(); + } + } + } catch (IOException e) { + System.err.println("保存文件失败: " + e.getMessage()); + } + } + + /** + * 加载用户的历史题目用于查重 + */ + public void loadHistoryQuestions(String username, java.util.Set questionSet) { + File userDir = new File(baseDir + File.separator + username); + if (!userDir.exists() || !userDir.isDirectory()) { + return; + } + + File[] files = userDir.listFiles((dir, name) -> name.endsWith(".txt")); + if (files == null) return; + + for (File file : files) { + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + String line; + while ((line = reader.readLine()) != null) { + if (line.matches("^\\d+\\.\\s+.+")) { + String question = line.substring(line.indexOf(".") + 1).trim(); + questionSet.add(normalizeQuestion(question)); + } + } + } catch (IOException e) { + System.err.println("读取历史题目失败: " + e.getMessage()); + } + } + } + + /** + * 规范化题目 + */ + private String normalizeQuestion(String question) { + return question.replaceAll("\\s+", "").toLowerCase(); + } +} \ No newline at end of file diff --git a/src/MathGenerator.java b/src/MathGenerator.java new file mode 100644 index 0000000..f7c617c --- /dev/null +++ b/src/MathGenerator.java @@ -0,0 +1,26 @@ + +import java.util.List; +/** + * 数学题目生成器接口 + */ +public interface MathGenerator { + /** + * 生成指定数量的题目 + */ + List generateQuestions(int count); + + /** + * 切换难度级别 + */ + void switchDifficulty(String level); + + /** + * 用户登录 + */ + boolean login(String username, String password); + + /** + * 保存题目到文件 + */ + void saveToFile(List questions); +} \ No newline at end of file diff --git a/src/MathTestGenerator.java b/src/MathTestGenerator.java new file mode 100644 index 0000000..ad7a0c5 --- /dev/null +++ b/src/MathTestGenerator.java @@ -0,0 +1,185 @@ +import entities.User; +import entities.UserType; +import java.util.*; + +/** + * 中小学数学卷子自动生成程序 - 主类 + * 作者:张三 + * 班级:软1班 + */ +public class MathTestGenerator extends AbstractMathGenerator { + private final UserService userService; + private final QuestionService questionService; + private final FileService fileService; + private final ValidationService validationService; + private final Scanner scanner; + + private User currentUser; + + public MathTestGenerator() { + this.userService = new UserService(); + this.questionService = new QuestionService(); + this.fileService = new FileService(); + this.validationService = new ValidationService(); + this.scanner = new Scanner(System.in); + } + + @Override + public boolean login(String username, String password) { + User user = userService.authenticate(username, password); + if (user != null) { + this.currentUser = user; + this.currentDifficulty = user.getType().getName(); + + System.out.println("当前选择为" + currentDifficulty + "出题"); + + // 加载历史题目用于查重 + Set historyQuestions = new HashSet<>(); + fileService.loadHistoryQuestions(username, historyQuestions); + // 这里需要将历史题目传递给questionService + return true; + } + return false; + } + + @Override + public List generateQuestions(int count) { + if (currentUser == null) { + throw new IllegalStateException("请先登录"); + } + + questionService.setCurrentType(UserType.fromName(currentDifficulty)); + List questions = new ArrayList<>(); + + for (int i = 0; i < count; i++) { + questions.add(questionService.generateQuestion()); + } + + return questions; + } + + @Override + public void switchDifficulty(String level) { + + this.currentDifficulty = level; + System.out.println("准备生成" + level + "数学题目,请输入生成题目数量"); + } + + @Override + public void saveToFile(List questions) { + fileService.saveQuestions(currentUser.getUsername(), questions); + } + + /** + * 启动程序 + */ + public void start() { + System.out.println("=== 中小学数学卷子自动生成程序 ==="); + + while (true) { + if (performLogin()) { + + generatePapers(); + } + } + } + + /** + * 执行登录流程 + */ + private boolean performLogin() { + while (true) { + System.out.print("请输入用户名和密码(用空格隔开,输入exit退出程序):"); + String input = scanner.nextLine().trim(); + + if (input.equalsIgnoreCase("exit")) { + System.exit(0); + } + + if (!validationService.isValidCredentialsFormat(input)) { + System.out.println("请输入正确的用户名、密码"); + continue; + } + + String[] credentials = input.split("\\s+"); + String username = credentials[0]; + String password = credentials[1]; + + if (login(username, password)) { + return true; + } else { + System.out.println("请输入正确的用户名、密码"); + } + } + } + + /** + * 生成试卷流程 + */ + private void generatePapers() { + while (true) { + System.out.print("准备生成" + currentDifficulty + + "数学题目,请输入生成题目数量(10-30,输入-1退出当前用户):"); + + String input = scanner.nextLine().trim(); + + if (input.equals("-1")) { + currentUser = null; + currentDifficulty = null; + questionService.clearGeneratedQuestions(); + System.out.println("已退出当前用户,请重新登录"); + return; + } + + if (validationService.isValidSwitchCommand(input)) { + + handleSwitchCommand(input); + continue; + } + + if (!validationService.isValidQuestionCount(input)) { + System.out.println("题目数量应在10-30之间"); + continue; + } + + try { + int count = Integer.parseInt(input); + List questions = generateQuestions(count); + saveToFile(questions); + System.out.println("试卷生成成功!生成题目数量:" + questions.size()); + } catch (Exception e) { + System.out.println("生成题目失败: " + e.getMessage()); + } + } + } + + /** + * 处理切换命令 + */ + private void handleSwitchCommand(String input) { + String level = validationService.extractDifficulty(input); + while(!level.equals("小学")&&!level.equals("初中")&&!level.equals("高中")) { + System.out.print("请输入小学、初中和高中三个选项中的一个\n"); + Scanner sc = new Scanner(System.in); + + level = validationService.extractDifficulty(sc.nextLine().trim()); + + } + try { + switchDifficulty(level); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + + + + } + + /** + * 主方法 + */ + public static void main(String[] args) { + MathTestGenerator generator = new MathTestGenerator(); + generator.start(); + } +} \ No newline at end of file diff --git a/src/Operator.java b/src/Operator.java new file mode 100644 index 0000000..41a1e24 --- /dev/null +++ b/src/Operator.java @@ -0,0 +1,32 @@ +package entities; + +/** + * 运算符枚举 + */ +public enum Operator { + ADD("+", 1, "小学初中高中"), + SUBTRACT("-", 1, "小学初中高中"), + MULTIPLY("*", 2, "小学初中高中"), + DIVIDE("/", 2, "小学初中高中"), + POWER("^", 3, "初中高中"), + SQRT("√", 3, "初中高中"), + SIN("sin", 4, "高中"), + COS("cos", 4, "高中"), + TAN("tan", 4, "高中"); + + private final String symbol; + private final int priority; + private final String allowedLevels; + + Operator(String symbol, int priority, String allowedLevels) { + this.symbol = symbol; + this.priority = priority; + this.allowedLevels = allowedLevels; + } + + public String getSymbol() { return symbol; } + public int getPriority() { return priority; } + public boolean isAllowedForLevel(String level) { + return allowedLevels.contains(level); + } +} \ No newline at end of file diff --git a/src/QuestionService.java b/src/QuestionService.java new file mode 100644 index 0000000..2d1261a --- /dev/null +++ b/src/QuestionService.java @@ -0,0 +1,192 @@ +import entities.Operator; +import entities.UserType; +import java.util.*; + +/** + * 题目生成服务 + */ +public class QuestionService { + private final Random random; + private final Set generatedQuestions; + private UserType currentType; + + public QuestionService() { + this.random = new Random(); + this.generatedQuestions = new HashSet<>(); + } + + /** + * 设置当前题目类型 + */ + public void setCurrentType(UserType type) { + this.currentType = type; + } + + /** + * 生成单个题目 + */ + public String generateQuestion() { + String question; + int attempts = 0; + + do { + question = generateUniqueQuestion(); + attempts++; + } while (generatedQuestions.contains(normalizeQuestion(question)) && attempts < 100); + + generatedQuestions.add(normalizeQuestion(question)); + return question; + } + + /** + * 生成唯一题目 + */ + private String generateUniqueQuestion() { + int operandCount = random.nextInt(5) + 1; + List operands = generateOperands(operandCount); + List operators = generateOperators(operandCount - 1); + + return buildExpression(operands, operators); + } + + /** + * 生成操作数 + */ + private List generateOperands(int count) { + List operands = new ArrayList<>(); + for (int i = 0; i < count; i++) { + operands.add(random.nextInt(100) + 1); + } + return operands; + } + + /** + * 生成运算符 + */ + private List generateOperators(int count) { + List operators = new ArrayList<>(); + List allowedOperators = getAllowedOperators(); + + // 确保符合难度要求 + if (currentType == UserType.JUNIOR) { + ensureJuniorRequirement(operators, allowedOperators, count); + } else if (currentType == UserType.SENIOR) { + ensureSeniorRequirement(operators, allowedOperators, count); + } else { + for (int i = 0; i < count; i++) { + operators.add(allowedOperators.get(random.nextInt(allowedOperators.size()))); + } + } + + return operators; + } + + /** + * 获取允许的运算符 + */ + private List getAllowedOperators() { + List allowed = new ArrayList<>(); + String level = currentType.getName(); + + for (Operator op : Operator.values()) { + if (op.isAllowedForLevel(level)) { + allowed.add(op); + } + } + return allowed; + } + + /** + * 确保初中题目要求(包含平方或开根号) + */ + private void ensureJuniorRequirement(List operators, List allowed, int count) { + boolean hasSpecialOp = false; + + for (int i = 0; i < count; i++) { + Operator op = allowed.get(random.nextInt(allowed.size())); + operators.add(op); + if (op == Operator.POWER || op == Operator.SQRT) { + hasSpecialOp = true; + } + } + + if (!hasSpecialOp && count > 0) { + int index = random.nextInt(operators.size()); + operators.set(index, random.nextBoolean() ? Operator.POWER : Operator.SQRT); + } + } + + /** + * 确保高中题目要求(包含三角函数) + */ + private void ensureSeniorRequirement(List operators, List allowed, int count) { + boolean hasTrigOp = false; + + for (int i = 0; i < count; i++) { + Operator op = allowed.get(random.nextInt(allowed.size())); + operators.add(op); + if (op == Operator.SIN || op == Operator.COS || op == Operator.TAN) { + hasTrigOp = true; + } + } + + if (!hasTrigOp && count > 0) { + int index = random.nextInt(operators.size()); + Operator[] trigOps = {Operator.SIN, Operator.COS, Operator.TAN}; + operators.set(index, trigOps[random.nextInt(trigOps.length)]); + } + } + + /** + * 构建表达式 + */ + private String buildExpression(List operands, List operators) { + StringBuilder expression = new StringBuilder(); + + expression.append(operands.get(0)); + + for (int i = 0; i < operators.size(); i++) { + Operator op = operators.get(i); + int operand = operands.get(i + 1); + + switch (op) { + case SQRT: + expression.insert(0, "√(").append(")"); + expression.append(op.getSymbol()).append(operand); + break; + case SIN: + case COS: + case TAN: + expression.append(op.getSymbol()).append("(").append(operand).append(")"); + break; + default: + expression.append(" ").append(op.getSymbol()).append(" ").append(operand); + } + } + + // 随机添加括号 + if (operands.size() > 2 && random.nextBoolean()) { + expression.insert(0, "("); + int spaceIndex = expression.indexOf(" ", expression.indexOf(" ") + 1); + if (spaceIndex != -1) { + expression.insert(spaceIndex + 1, ")"); + } + } + + return expression.toString(); + } + + /** + * 规范化题目用于查重 + */ + private String normalizeQuestion(String question) { + return question.replaceAll("\\s+", "").toLowerCase(); + } + + /** + * 清空已生成题目记录(用户退出时) + */ + public void clearGeneratedQuestions() { + generatedQuestions.clear(); + } +} \ No newline at end of file diff --git a/src/User.java b/src/User.java new file mode 100644 index 0000000..79b1467 --- /dev/null +++ b/src/User.java @@ -0,0 +1,21 @@ +package entities; + +/** + * 用户实体类 + */ +public class User { + private String username; + private String password; + private UserType type; + + public User(String username, String password, UserType type) { + this.username = username; + this.password = password; + this.type = type; + } + + // Getter方法 + public String getUsername() { return username; } + public String getPassword() { return password; } + public UserType getType() { return type; } +} \ No newline at end of file diff --git a/src/UserService.java b/src/UserService.java new file mode 100644 index 0000000..92085fd --- /dev/null +++ b/src/UserService.java @@ -0,0 +1,54 @@ +import entities.User; +import entities.UserType; +import java.util.HashMap; +import java.util.Map; + +/** + * 用户管理服务 + */ +public class UserService { + private final Map users; + + public UserService() { + this.users = new HashMap<>(); + initializeUsers(); + } + + /** + * 初始化用户数据 + */ + private void initializeUsers() { + // 小学账户 + users.put("张三1", new User("张三1", "123", UserType.PRIMARY)); + users.put("张三2", new User("张三2", "123", UserType.PRIMARY)); + users.put("张三3", new User("张三3", "123", UserType.PRIMARY)); + + // 初中账户 + users.put("李四1", new User("李四1", "123", UserType.JUNIOR)); + users.put("李四2", new User("李四2", "123", UserType.JUNIOR)); + users.put("李四3", new User("李四3", "123", UserType.JUNIOR)); + + // 高中账户 + users.put("王五1", new User("王五1", "123", UserType.SENIOR)); + users.put("王五2", new User("王五2", "123", UserType.SENIOR)); + users.put("王五3", new User("王五3", "123", UserType.SENIOR)); + } + + /** + * 用户认证 + */ + public User authenticate(String username, String password) { + User user = users.get(username); + if (user != null && user.getPassword().equals(password)) { + return user; + } + return null; + } + + /** + * 获取所有用户(用于测试) + */ + public Map getUsers() { + return new HashMap<>(users); + } +} \ No newline at end of file diff --git a/src/UserType.java b/src/UserType.java new file mode 100644 index 0000000..8317ac5 --- /dev/null +++ b/src/UserType.java @@ -0,0 +1,32 @@ +package entities; + +/** + * 用户类型枚举 + */ +public enum UserType { + PRIMARY("小学"), + JUNIOR("初中"), + SENIOR("高中"); + + private final String name; + + UserType(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + /** + * 根据中文名称获取枚举值 + */ + public static UserType fromName(String name) { + for (UserType type : values()) { + if (type.name.equals(name)) { + return type; + } + } + throw new IllegalArgumentException("无效的用户类型: " + name); + } +} \ No newline at end of file diff --git a/src/ValidationService.java b/src/ValidationService.java new file mode 100644 index 0000000..9c1a409 --- /dev/null +++ b/src/ValidationService.java @@ -0,0 +1,49 @@ +import java.util.regex.Pattern; + +/** + * 输入验证服务 + */ +public class ValidationService { + + /** + * 验证切换命令格式 + */ + public boolean isValidSwitchCommand(String input) { + if (!input.startsWith("切换为")) { + return false; + } + + return true; + } + + /** + * 验证题目数量 + */ + public boolean isValidQuestionCount(String input) { + if (input.equals("-1")) { + return true; + } + + try { + int count = Integer.parseInt(input); + return count >= 10 && count <= 30; + } catch (NumberFormatException e) { + return false; + } + } + + /** + * 验证用户名密码格式 + */ + public boolean isValidCredentialsFormat(String input) { + String[] parts = input.split("\\s+"); + return parts.length == 2; + } + + /** + * 提取难度级别 + */ + public String extractDifficulty(String switchCommand) { + return switchCommand.substring(3).trim(); + } +} \ No newline at end of file