You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

197 lines
6.0 KiB

import java.util.Random;
import java.util.Set;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.Collectors;
/**
* 工具类:负责生成不同难度的数学题目。
*/
public class Generator {
private static final Random RAND = new Random();
private static final int MAX_ATTEMPTS = 2000;
/**
* 内部类:按用户难度等级生成题目。
*/
static class QuestionGenerator {
private final Login.Level level;
private final Set<String> existing;
QuestionGenerator(Login.Level level, List<String> existingQuestions) {
this.level = level;
this.existing = existingQuestions.stream().map(String::trim).collect(Collectors.toSet());
}
/**
* 生成一份试卷。
*
* @param n 需要生成的题目数量
* @return 生成的题目列表
*/
List<String> generatePaper(int n) {
Set<String> generated = new LinkedHashSet<>();
// 防止死循环
int attempts = 0;
while (generated.size() < n && attempts < MAX_ATTEMPTS) {
attempts++;
String q = generateOneQuestion();
// 统一去掉题号与多余空白来比对
String key = normalize(q);
//与文件内+本次前面生成的查重
if (!existing.contains(key) && !generated.contains(key)) {
generated.add(q);
}
}
if (generated.size() < n) {
System.out.println(
"注意:无法生成足够的不重复题目,已生成 "
+ generated.size() + " 道题(请求 " + n + " 道)");
}
return new ArrayList<>(generated);
}
//去空格
private String normalize(String s) {
return s.replaceAll("\\s+", "").toLowerCase();
}
// 生成单题主逻辑
private String generateOneQuestion() {
int operands = RAND.nextInt(5) + 1; // 保证 1..5 个操作数
int operands_p = RAND.nextInt(4) + 2; // 保证 2..5 个操作数
return switch (level) {
case PRIMARY -> genPrimary(operands_p);
case MIDDLE -> genMiddle(operands);
case HIGH -> genHigh(operands);
};
}
// 生成小学题(只有 + - * / 和括号)
private String genPrimary(int operands) {
if (operands == 1) {
return String.valueOf(randInt(1, 100));
}
List<String> ops = Arrays.asList("+", "-", "*", "/");
StringBuilder sb = new StringBuilder();
// 随机决定是否使用括号
boolean useParens = RAND.nextBoolean();
if (useParens && operands >= 3 && RAND.nextBoolean()) {
// 构造 (a op b) op c ...
sb.append("(");
sb.append(randInt(1, 100)).append(" ").append(randomChoice(ops)).append(" ")
.append(randInt(1, 100));
sb.append(")");
for (int i = 2; i < operands; i++) {
sb.append(" ").append(randomChoice(ops)).append(" ").append(randInt(1, 100));
}
} else {
// 直连
sb.append(randInt(1, 100));
for (int i = 1; i < operands; i++) {
sb.append(" ").append(randomChoice(ops)).append(" ").append(randInt(1, 100));
}
}
return sb.toString();
}
// 生成初中题:至少包含一个 ^2 或 sqrt()
private String genMiddle(int operands) {
// 基本表达式生成,后插入平方或开根号
String expr = genPrimary(operands); // 基本算术
// 2选1
if (RAND.nextBoolean()) {
// 找一个数字位置并替换为 (x)^2
expr = applySquare(expr);
} else {
expr = applySqrt(expr);
}
return expr;
}
// 生成高中题:至少包含 sin/cos/tan
private String genHigh(int operands) {
String expr = genPrimary(operands);
expr = applyTrig(expr);
return expr;
}
// 把表达式中某个数字替换为 (x)^2
private String applySquare(String expr) {
// 寻找所有数字的片段
List<int[]> spans = findNumberSpans(expr);
if (spans.isEmpty()) {
return expr + "^2";
}
int[] s = spans.get(RAND.nextInt(spans.size()));
String before = expr.substring(0, s[0]);
String num = expr.substring(s[0], s[1]);
String after = expr.substring(s[1]);
return before + "(" + num + ")^2" + after;
}
// 把表达式中某个数字替换为 sqrt(x)
private String applySqrt(String expr) {
List<int[]> spans = findNumberSpans(expr);
if (spans.isEmpty()) {
return "sqrt(" + expr + ")";
}
int[] s = spans.get(RAND.nextInt(spans.size()));
String before = expr.substring(0, s[0]);
String num = expr.substring(s[0], s[1]);
String after = expr.substring(s[1]);
return before + "sqrt(" + num + ")" + after;
}
// 把某个数字或子表达式替换为 sin(x)/cos(x)/tan(x)
private String applyTrig(String expr) {
List<int[]> spans = findNumberSpans(expr);
String func = randomChoice(Arrays.asList("sin", "cos", "tan"));
if (spans.isEmpty()) {
return func + "(" + expr + ")";
}
int[] s = spans.get(RAND.nextInt(spans.size()));
String before = expr.substring(0, s[0]);
String num = expr.substring(s[0], s[1]);
String after = expr.substring(s[1]);
return before + func + "(" + num + ")" + after;
}
// 找出数字位置
private List<int[]> findNumberSpans(String expr) {
List<int[]> spans = new ArrayList<>();
char[] chs = expr.toCharArray();
int i = 0, n = chs.length;
while (i < n) {
if (Character.isDigit(chs[i])) {
int j = i;
while (j < n && (Character.isDigit(chs[j]))) {
j++;
}
spans.add(new int[]{i, j});
i = j;
} else {
i++;
}
}
return spans;
}
//a..b随机数
private int randInt(int a, int b) {
return RAND.nextInt(b - a + 1) + a;
}
//抽签功能
private <T> T randomChoice(List<T> list) {
return list.get(RAND.nextInt(list.size()));
}
}
}