|
|
|
|
@ -1,384 +0,0 @@
|
|
|
|
|
package com.example.mathsystemtogether;
|
|
|
|
|
//TIP 要<b>运行</b>代码,请按 <shortcut actionId="Run"/> 或
|
|
|
|
|
// 点击装订区域中的 <icon src="AllIcons.Actions.Execute"/> 图标。
|
|
|
|
|
import java.io.BufferedReader;
|
|
|
|
|
import java.io.BufferedWriter;
|
|
|
|
|
import java.io.File;
|
|
|
|
|
import java.io.FileInputStream;
|
|
|
|
|
import java.io.FileWriter;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.io.InputStreamReader;
|
|
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
|
|
import java.time.LocalDateTime;
|
|
|
|
|
import java.time.format.DateTimeFormatter;
|
|
|
|
|
import java.util.*;
|
|
|
|
|
|
|
|
|
|
public class Main {
|
|
|
|
|
enum Level {
|
|
|
|
|
小学, 初中, 高中
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static class Account {
|
|
|
|
|
final String username;
|
|
|
|
|
final String password;
|
|
|
|
|
final Level level;
|
|
|
|
|
|
|
|
|
|
Account(String username, String password, Level level) {
|
|
|
|
|
this.username = username;
|
|
|
|
|
this.password = password;
|
|
|
|
|
this.level = level;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static final Map<String, Account> USER_MAP = new HashMap<>();
|
|
|
|
|
private static final String OUTPUT_ROOT = "papers"; // 每个账号一个子文件夹
|
|
|
|
|
private static final Random RANDOM = new Random();
|
|
|
|
|
|
|
|
|
|
// 常量定义
|
|
|
|
|
private static final int MIN_QUESTIONS = 10;
|
|
|
|
|
private static final int MAX_QUESTIONS = 30;
|
|
|
|
|
private static final int MAX_ATTEMPTS_MULTIPLIER = 200;
|
|
|
|
|
private static final int MAX_OPERAND_VALUE = 100;
|
|
|
|
|
private static final int MIN_OPERAND_VALUE = 1;
|
|
|
|
|
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
initAccounts();
|
|
|
|
|
Scanner scanner = new Scanner(System.in, StandardCharsets.UTF_8.name());
|
|
|
|
|
while (true) {
|
|
|
|
|
Account login = loginLoop(scanner);
|
|
|
|
|
if (login == null) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
Level currentLevel = login.level;
|
|
|
|
|
System.out.println("当前选择为" + currentLevel + "出题");
|
|
|
|
|
// 登录后的工作循环
|
|
|
|
|
System.out.println("准备生成" + currentLevel + "数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录)");
|
|
|
|
|
while (true) {
|
|
|
|
|
String line = readLineTrim(scanner);
|
|
|
|
|
if (line == null) return; // EOF
|
|
|
|
|
// 支持命令:切换为XX
|
|
|
|
|
if (line.startsWith("切换为")) {
|
|
|
|
|
String target = line.substring("切换为".length()).trim();
|
|
|
|
|
Level targetLevel = parseLevel(target);
|
|
|
|
|
if (targetLevel == null) {
|
|
|
|
|
System.out.println("请输入小学、初中和高中三个选项中的一个");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
currentLevel = targetLevel;
|
|
|
|
|
System.out.println("准备生成" + currentLevel + "数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录)");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
int count;
|
|
|
|
|
try {
|
|
|
|
|
count = Integer.parseInt(line);
|
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
|
System.out.println("请输入10-30之间的整数,或-1退出登录,或输入‘切换为XX’");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (count == -1) {
|
|
|
|
|
break; // 退出当前账号,重新登录
|
|
|
|
|
}
|
|
|
|
|
if (count < 10 || count > 30) {
|
|
|
|
|
System.out.println("题目数量应为10-30之间(含),或-1退出登录");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 生成题目并去重
|
|
|
|
|
Set<String> history = loadHistoryQuestions(login.username);
|
|
|
|
|
List<String> paper = generateUniquePaper(currentLevel, count, history);
|
|
|
|
|
// 保存
|
|
|
|
|
try {
|
|
|
|
|
File saved = savePaper(login.username, paper);
|
|
|
|
|
System.out.println("已生成并保存:" + saved.getAbsolutePath());
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
System.out.println("保存失败:" + e.getMessage());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void initAccounts() {
|
|
|
|
|
// 小学三个账号
|
|
|
|
|
USER_MAP.put("张三1", new Account("张三1", "123", Level.小学));
|
|
|
|
|
USER_MAP.put("张三2", new Account("张三2", "123", Level.小学));
|
|
|
|
|
USER_MAP.put("张三3", new Account("张三3", "123", Level.小学));
|
|
|
|
|
// 初中三个账号
|
|
|
|
|
USER_MAP.put("李四1", new Account("李四1", "123", Level.初中));
|
|
|
|
|
USER_MAP.put("李四2", new Account("李四2", "123", Level.初中));
|
|
|
|
|
USER_MAP.put("李四3", new Account("李四3", "123", Level.初中));
|
|
|
|
|
// 高中三个账号
|
|
|
|
|
USER_MAP.put("王五1", new Account("王五1", "123", Level.高中));
|
|
|
|
|
USER_MAP.put("王五2", new Account("王五2", "123", Level.高中));
|
|
|
|
|
USER_MAP.put("王五3", new Account("王五3", "123", Level.高中));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static Account loginLoop(Scanner scanner) {
|
|
|
|
|
System.out.println("请输入用户名和密码(用空格分隔),或输入-1退出:");
|
|
|
|
|
while (true) {
|
|
|
|
|
String line = readLineTrim(scanner);
|
|
|
|
|
if (line == null) return null; // EOF 退出
|
|
|
|
|
if ("-1".equals(line)) return null; // 主动输入-1退出
|
|
|
|
|
String[] parts = line.split("\\s+");
|
|
|
|
|
if (parts.length != 2) {
|
|
|
|
|
System.out.println("请输入正确的用户名、密码,或输入-1退出");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
String username = parts[0];
|
|
|
|
|
String password = parts[1];
|
|
|
|
|
Account acc = USER_MAP.get(username);
|
|
|
|
|
if (acc != null && Objects.equals(acc.password, password)) {
|
|
|
|
|
return acc;
|
|
|
|
|
}
|
|
|
|
|
System.out.println("请输入正确的用户名、密码,或输入-1退出");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static String readLineTrim(Scanner scanner) {
|
|
|
|
|
if (!scanner.hasNextLine()) return null;
|
|
|
|
|
String s = scanner.nextLine();
|
|
|
|
|
if (s == null) return null;
|
|
|
|
|
return s.trim();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static Level parseLevel(String s) {
|
|
|
|
|
for (Level lv : Level.values()) {
|
|
|
|
|
if (lv.name().equals(s)) return lv;
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 通用的输入验证方法
|
|
|
|
|
private static boolean isValidQuestionCount(int count) {
|
|
|
|
|
return count >= MIN_QUESTIONS && count <= MAX_QUESTIONS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static List<String> generateUniquePaper(Level level, int count, Set<String> history) {
|
|
|
|
|
Set<String> set = new LinkedHashSet<>();
|
|
|
|
|
int attempts = 0;
|
|
|
|
|
int maxAttempts = count * MAX_ATTEMPTS_MULTIPLIER; // 足够多的尝试次数以避免死循环
|
|
|
|
|
while (set.size() < count && attempts < maxAttempts) {
|
|
|
|
|
String q = generateQuestion(level);
|
|
|
|
|
attempts++;
|
|
|
|
|
if (!history.contains(q) && !set.contains(q)) {
|
|
|
|
|
set.add(q);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (set.size() < count) {
|
|
|
|
|
System.out.println("注意:因去重限制,仅生成" + set.size() + "题");
|
|
|
|
|
}
|
|
|
|
|
return new ArrayList<>(set);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//==================== 面向接口的题目生成器 ====================
|
|
|
|
|
interface QuestionGenerator {
|
|
|
|
|
String generate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 通用字符串拼接方法,避免重复代码
|
|
|
|
|
private static String buildExpression(List<String> tokens) {
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
|
for (String tk : tokens) sb.append(tk);
|
|
|
|
|
sb.append(" =");
|
|
|
|
|
return sb.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 通用的基础表达式生成逻辑
|
|
|
|
|
private static List<String> generateBaseExpression(int minOperands, int maxOperands, String operators) {
|
|
|
|
|
int operands = minOperands + RANDOM.nextInt(maxOperands - minOperands + 1);
|
|
|
|
|
List<String> tokens = new ArrayList<>();
|
|
|
|
|
int first = MIN_OPERAND_VALUE + RANDOM.nextInt(MAX_OPERAND_VALUE);
|
|
|
|
|
tokens.add(String.valueOf(first));
|
|
|
|
|
for (int i = 1; i < operands; i++) {
|
|
|
|
|
char op = pickOperator(operators);
|
|
|
|
|
int n = MIN_OPERAND_VALUE + RANDOM.nextInt(MAX_OPERAND_VALUE);
|
|
|
|
|
if (op == '/') {
|
|
|
|
|
int prevNumber = extractLastPureNumber(tokens);
|
|
|
|
|
if (prevNumber > 0) {
|
|
|
|
|
n = pickDivisor(prevNumber);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
tokens.add(" " + op + " ");
|
|
|
|
|
tokens.add(String.valueOf(n));
|
|
|
|
|
}
|
|
|
|
|
return tokens;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static class PrimaryQuestionGenerator implements QuestionGenerator {
|
|
|
|
|
@Override
|
|
|
|
|
public String generate() {
|
|
|
|
|
List<String> tokens = generateBaseExpression(2, 5, "+*/");
|
|
|
|
|
maybeInsertParentheses(tokens);
|
|
|
|
|
return buildExpression(tokens);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static class JuniorQuestionGenerator implements QuestionGenerator {
|
|
|
|
|
@Override
|
|
|
|
|
public String generate() {
|
|
|
|
|
List<String> tokens = generateBaseExpression(2, 5, "+-*/");
|
|
|
|
|
ensureJuniorFeature(tokens);
|
|
|
|
|
maybeInsertParentheses(tokens);
|
|
|
|
|
return buildExpression(tokens);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static class SeniorQuestionGenerator implements QuestionGenerator {
|
|
|
|
|
@Override
|
|
|
|
|
public String generate() {
|
|
|
|
|
List<String> tokens = generateBaseExpression(3, 5, "+-*/");
|
|
|
|
|
ensureSeniorFeature(tokens);
|
|
|
|
|
maybeInsertParentheses(tokens);
|
|
|
|
|
return buildExpression(tokens);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static final Map<Level, QuestionGenerator> GENERATOR_MAP = new EnumMap<>(Level.class);
|
|
|
|
|
static {
|
|
|
|
|
GENERATOR_MAP.put(Level.小学, new PrimaryQuestionGenerator());
|
|
|
|
|
GENERATOR_MAP.put(Level.初中, new JuniorQuestionGenerator());
|
|
|
|
|
GENERATOR_MAP.put(Level.高中, new SeniorQuestionGenerator());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static String generateQuestion(Level level) {
|
|
|
|
|
QuestionGenerator generator = GENERATOR_MAP.get(level);
|
|
|
|
|
if (generator == null) generator = GENERATOR_MAP.get(Level.小学);
|
|
|
|
|
return generator.generate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 生成器实现已迁移到各具体类
|
|
|
|
|
|
|
|
|
|
private static char pickOperator(String candidates) {
|
|
|
|
|
int idx = RANDOM.nextInt(candidates.length());
|
|
|
|
|
return candidates.charAt(idx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static int pickDivisor(int dividend) {
|
|
|
|
|
dividend = Math.abs(dividend);
|
|
|
|
|
if (dividend == 0) return 1;
|
|
|
|
|
List<Integer> divisors = new ArrayList<>();
|
|
|
|
|
for (int d = 1; d <= Math.min(dividend, MAX_OPERAND_VALUE); d++) {
|
|
|
|
|
if (dividend % d == 0) {
|
|
|
|
|
divisors.add(d);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return divisors.get(RANDOM.nextInt(divisors.size()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static void maybeInsertParentheses(List<String> tokens) {
|
|
|
|
|
int parenCount = RANDOM.nextInt(3); // 0,1,2 对
|
|
|
|
|
for (int k = 0; k < parenCount; k++) {
|
|
|
|
|
int terms = (tokens.size() + 1) / 2; // 操作数个数
|
|
|
|
|
if (terms < 3) break;
|
|
|
|
|
int leftIndex = 0;
|
|
|
|
|
int rightIndex = 0;
|
|
|
|
|
for (int t = 0; t < 10; t++) {
|
|
|
|
|
int li = RANDOM.nextInt(terms - 1);
|
|
|
|
|
int ri = li + 1 + RANDOM.nextInt(terms - li - 1);
|
|
|
|
|
leftIndex = li * 2;
|
|
|
|
|
rightIndex = ri * 2;
|
|
|
|
|
if (rightIndex - leftIndex >= 4) break; // 至少一个运算符
|
|
|
|
|
}
|
|
|
|
|
tokens.add(leftIndex, "(");
|
|
|
|
|
tokens.add(rightIndex + 2, ")");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static int extractLastPureNumber(List<String> tokens) {
|
|
|
|
|
for (int i = tokens.size() - 1; i >= 0; i--) {
|
|
|
|
|
String tk = tokens.get(i).trim();
|
|
|
|
|
if (tk.matches("\\d+")) {
|
|
|
|
|
return Integer.parseInt(tk);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void ensureJuniorFeature(List<String> tokens) {
|
|
|
|
|
boolean hasSquareOrSqrt = false;
|
|
|
|
|
for (String tk : tokens) {
|
|
|
|
|
if (tk.contains("^2") || tk.contains("√")) { hasSquareOrSqrt = true; break; }
|
|
|
|
|
}
|
|
|
|
|
if (!hasSquareOrSqrt) {
|
|
|
|
|
// 选择一个操作数位置,50% 概率平方,50% 概率开根号
|
|
|
|
|
List<Integer> operandIndexes = new ArrayList<>();
|
|
|
|
|
for (int i = 0; i < tokens.size(); i += 2) operandIndexes.add(i);
|
|
|
|
|
if (operandIndexes.isEmpty()) return;
|
|
|
|
|
int idxToken = operandIndexes.get(RANDOM.nextInt(operandIndexes.size()));
|
|
|
|
|
String val = tokens.get(idxToken).trim();
|
|
|
|
|
if (RANDOM.nextBoolean()) {
|
|
|
|
|
// 平方
|
|
|
|
|
tokens.set(idxToken, val + "^2");
|
|
|
|
|
} else {
|
|
|
|
|
// 开根号
|
|
|
|
|
tokens.set(idxToken, "√(" + val + ")");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void ensureSeniorFeature(List<String> tokens) {
|
|
|
|
|
boolean hasTrig = false;
|
|
|
|
|
for (String tk : tokens) {
|
|
|
|
|
if (tk.contains("sin") || tk.contains("cos") || tk.contains("tan")) { hasTrig = true; break; }
|
|
|
|
|
}
|
|
|
|
|
if (!hasTrig) {
|
|
|
|
|
List<Integer> operandIndexes = new ArrayList<>();
|
|
|
|
|
for (int i = 0; i < tokens.size(); i += 2) operandIndexes.add(i);
|
|
|
|
|
if (operandIndexes.isEmpty()) return;
|
|
|
|
|
int idxToken = operandIndexes.get(RANDOM.nextInt(operandIndexes.size()));
|
|
|
|
|
String val = tokens.get(idxToken).trim();
|
|
|
|
|
String func;
|
|
|
|
|
switch (RANDOM.nextInt(3)) {
|
|
|
|
|
case 0: func = "sin"; break;
|
|
|
|
|
case 1: func = "cos"; break;
|
|
|
|
|
default: func = "tan"; break;
|
|
|
|
|
}
|
|
|
|
|
tokens.set(idxToken, func + "(" + val + ")");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static Set<String> loadHistoryQuestions(String username) {
|
|
|
|
|
Set<String> set = new HashSet<>();
|
|
|
|
|
File folder = new File(OUTPUT_ROOT, username);
|
|
|
|
|
if (!folder.exists() || !folder.isDirectory()) return set;
|
|
|
|
|
File[] files = folder.listFiles((dir, name) -> name.endsWith(".txt"));
|
|
|
|
|
if (files == null) return set;
|
|
|
|
|
for (File f : files) {
|
|
|
|
|
try (BufferedReader br = new BufferedReader(
|
|
|
|
|
new InputStreamReader(new FileInputStream(f), StandardCharsets.UTF_8))) {
|
|
|
|
|
String line;
|
|
|
|
|
while ((line = br.readLine()) != null) {
|
|
|
|
|
line = line.trim();
|
|
|
|
|
if (line.isEmpty()) continue;
|
|
|
|
|
int dot = line.indexOf('.') ;
|
|
|
|
|
if (dot > 0) {
|
|
|
|
|
String maybe = line.substring(dot + 1).trim();
|
|
|
|
|
if (!maybe.isEmpty()) set.add(maybe);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (IOException ignored) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return set;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static File savePaper(String username, List<String> questions) throws IOException {
|
|
|
|
|
File folder = new File(OUTPUT_ROOT, username);
|
|
|
|
|
if (!folder.exists()) {
|
|
|
|
|
if (!folder.mkdirs()) throw new IOException("无法创建目录:" + folder.getAbsolutePath());
|
|
|
|
|
}
|
|
|
|
|
LocalDateTime now = LocalDateTime.now();
|
|
|
|
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-M-d-H-m-s");
|
|
|
|
|
String filename = now.format(formatter) + ".txt";
|
|
|
|
|
File out = new File(folder, filename);
|
|
|
|
|
try (BufferedWriter bw = new BufferedWriter(new FileWriter(out, StandardCharsets.UTF_8))) {
|
|
|
|
|
for (int i = 0; i < questions.size(); i++) {
|
|
|
|
|
bw.write((i + 1) + ". " + questions.get(i));
|
|
|
|
|
bw.newLine();
|
|
|
|
|
bw.newLine();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
}
|