|
|
|
@ -32,6 +32,13 @@ public class Main {
|
|
|
|
|
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();
|
|
|
|
@ -44,8 +51,8 @@ public class Main {
|
|
|
|
|
Level currentLevel = login.level;
|
|
|
|
|
System.out.println("当前选择为" + currentLevel + "出题");
|
|
|
|
|
// 登录后的工作循环
|
|
|
|
|
System.out.println("准备生成" + currentLevel + "数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录)");
|
|
|
|
|
while (true) {
|
|
|
|
|
System.out.println("准备生成" + currentLevel + "数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录)");
|
|
|
|
|
String line = readLineTrim(scanner);
|
|
|
|
|
if (line == null) return; // EOF
|
|
|
|
|
// 支持命令:切换为XX
|
|
|
|
@ -57,6 +64,7 @@ public class Main {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
currentLevel = targetLevel;
|
|
|
|
|
System.out.println("准备生成" + currentLevel + "数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录)");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
int count;
|
|
|
|
@ -137,11 +145,16 @@ public class Main {
|
|
|
|
|
}
|
|
|
|
|
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 * 200; // 足够多的尝试次数以避免死循环
|
|
|
|
|
int maxAttempts = count * MAX_ATTEMPTS_MULTIPLIER; // 足够多的尝试次数以避免死循环
|
|
|
|
|
while (set.size() < count && attempts < maxAttempts) {
|
|
|
|
|
String q = generateQuestion(level);
|
|
|
|
|
attempts++;
|
|
|
|
@ -155,27 +168,29 @@ public class Main {
|
|
|
|
|
return new ArrayList<>(set);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static String generateQuestion(Level level) {
|
|
|
|
|
// 统一表达式末尾不加等号,以字符串完全相等进行去重
|
|
|
|
|
return switch (level) {
|
|
|
|
|
case 小学 -> generatePrimary();
|
|
|
|
|
case 初中 -> generateJunior();
|
|
|
|
|
case 高中 -> generateSenior();
|
|
|
|
|
default -> generatePrimary();
|
|
|
|
|
};
|
|
|
|
|
//==================== 面向接口的题目生成器 ====================
|
|
|
|
|
interface QuestionGenerator {
|
|
|
|
|
String generate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static String generatePrimary() {
|
|
|
|
|
// 仅使用 +, *, / 和括号,运算数 1-5 个,范围 1-100
|
|
|
|
|
int operands = 2 + RANDOM.nextInt(4); // 2-5
|
|
|
|
|
|
|
|
|
|
// 通用字符串拼接方法,避免重复代码
|
|
|
|
|
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 = 1 + RANDOM.nextInt(100);
|
|
|
|
|
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("+*/");
|
|
|
|
|
int n = 1 + RANDOM.nextInt(100);
|
|
|
|
|
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);
|
|
|
|
@ -184,73 +199,63 @@ public class Main {
|
|
|
|
|
tokens.add(" " + op + " ");
|
|
|
|
|
tokens.add(String.valueOf(n));
|
|
|
|
|
}
|
|
|
|
|
maybeInsertParentheses(tokens);
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
|
for (String tk : tokens) sb.append(tk);
|
|
|
|
|
return sb.toString();
|
|
|
|
|
return tokens;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static String generateJunior() {
|
|
|
|
|
// 至少包含一个 平方 或 开根号;允许 + - * / 和括号
|
|
|
|
|
int operands = 2 + RANDOM.nextInt(4); // 2-5
|
|
|
|
|
List<String> tokens = new ArrayList<>();
|
|
|
|
|
String first = String.valueOf(1 + RANDOM.nextInt(100));
|
|
|
|
|
tokens.add(first);
|
|
|
|
|
for (int i = 1; i < operands; i++) {
|
|
|
|
|
char op = pickOperator("+-*/");
|
|
|
|
|
String n = String.valueOf(1 + RANDOM.nextInt(100));
|
|
|
|
|
tokens.add(" " + op + " ");
|
|
|
|
|
tokens.add(n);
|
|
|
|
|
static class PrimaryQuestionGenerator implements QuestionGenerator {
|
|
|
|
|
@Override
|
|
|
|
|
public String generate() {
|
|
|
|
|
List<String> tokens = generateBaseExpression(2, 5, "+*/");
|
|
|
|
|
maybeInsertParentheses(tokens);
|
|
|
|
|
return buildExpression(tokens);
|
|
|
|
|
}
|
|
|
|
|
// 强制至少包含一个 平方 或 根号
|
|
|
|
|
ensureJuniorFeature(tokens);
|
|
|
|
|
maybeInsertParentheses(tokens);
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
|
for (String tk : tokens) sb.append(tk);
|
|
|
|
|
return sb.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static String generateSenior() {
|
|
|
|
|
// 至少包含一个 sin/cos/tan;允许 + - * / 和括号
|
|
|
|
|
int operands = 3 + RANDOM.nextInt(3); // 3-5
|
|
|
|
|
List<String> tokens = new ArrayList<>();
|
|
|
|
|
tokens.add(String.valueOf(1 + RANDOM.nextInt(100)));
|
|
|
|
|
for (int i = 1; i < operands; i++) {
|
|
|
|
|
char op = pickOperator("+-*/");
|
|
|
|
|
String n = String.valueOf(1 + RANDOM.nextInt(100));
|
|
|
|
|
tokens.add(" " + op + " ");
|
|
|
|
|
tokens.add(n);
|
|
|
|
|
static class JuniorQuestionGenerator implements QuestionGenerator {
|
|
|
|
|
@Override
|
|
|
|
|
public String generate() {
|
|
|
|
|
List<String> tokens = generateBaseExpression(2, 5, "+-*/");
|
|
|
|
|
ensureJuniorFeature(tokens);
|
|
|
|
|
maybeInsertParentheses(tokens);
|
|
|
|
|
return buildExpression(tokens);
|
|
|
|
|
}
|
|
|
|
|
ensureSeniorFeature(tokens);
|
|
|
|
|
maybeInsertParentheses(tokens);
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
|
for (String tk : tokens) sb.append(tk);
|
|
|
|
|
return sb.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 lastNumberForDivision(CharSequence currentExpr) {
|
|
|
|
|
// 粗略解析,找到最后一个数字(用于生成可整除的除法)
|
|
|
|
|
int i = currentExpr.length() - 1;
|
|
|
|
|
while (i >= 0 && !Character.isDigit(currentExpr.charAt(i))) i--;
|
|
|
|
|
int end = i;
|
|
|
|
|
while (i >= 0 && Character.isDigit(currentExpr.charAt(i))) i--;
|
|
|
|
|
int start = i + 1;
|
|
|
|
|
if (start <= end) {
|
|
|
|
|
return Integer.parseInt(currentExpr.subSequence(start, end + 1).toString());
|
|
|
|
|
}
|
|
|
|
|
return 1 + RANDOM.nextInt(100);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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, 100); d++) {
|
|
|
|
|
for (int d = 1; d <= Math.min(dividend, MAX_OPERAND_VALUE); d++) {
|
|
|
|
|
if (dividend % d == 0) {
|
|
|
|
|
divisors.add(d);
|
|
|
|
|
}
|
|
|
|
@ -258,21 +263,7 @@ public class Main {
|
|
|
|
|
return divisors.get(RANDOM.nextInt(divisors.size()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static String buildExpressionWithParentheses(List<Integer> numbers, List<Character> ops) {
|
|
|
|
|
// 已不再使用该构造器于主流程,保留供兼容
|
|
|
|
|
List<String> tokens = new ArrayList<>();
|
|
|
|
|
tokens.add(String.valueOf(numbers.get(0)));
|
|
|
|
|
for (int i = 0; i < ops.size(); i++) {
|
|
|
|
|
char op = ops.get(i);
|
|
|
|
|
int n = numbers.get(i + 1);
|
|
|
|
|
tokens.add(" " + op + " ");
|
|
|
|
|
tokens.add(String.valueOf(n));
|
|
|
|
|
}
|
|
|
|
|
maybeInsertParentheses(tokens);
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
|
for (String tk : tokens) sb.append(tk);
|
|
|
|
|
return sb.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static void maybeInsertParentheses(List<String> tokens) {
|
|
|
|
|
int parenCount = RANDOM.nextInt(3); // 0,1,2 对
|
|
|
|
@ -353,7 +344,8 @@ public class Main {
|
|
|
|
|
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))) {
|
|
|
|
|
try (BufferedReader br = new BufferedReader(
|
|
|
|
|
new InputStreamReader(new FileInputStream(f), StandardCharsets.UTF_8))) {
|
|
|
|
|
String line;
|
|
|
|
|
while ((line = br.readLine()) != null) {
|
|
|
|
|
line = line.trim();
|
|
|
|
|