|
|
|
|
@ -1,196 +1,190 @@
|
|
|
|
|
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.LinkedHashSet;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Random;
|
|
|
|
|
import java.util.Set;
|
|
|
|
|
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 {
|
|
|
|
|
public class Generator extends QuestionGenerator {
|
|
|
|
|
|
|
|
|
|
private final Login.Level level;
|
|
|
|
|
private final Set<String> existing;
|
|
|
|
|
public static final Random RAND = new Random();
|
|
|
|
|
public static final int MAX_ATTEMPTS = 2000;
|
|
|
|
|
|
|
|
|
|
QuestionGenerator(Login.Level level, List<String> existingQuestions) {
|
|
|
|
|
this.level = level;
|
|
|
|
|
this.existing = existingQuestions.stream().map(String::trim).collect(Collectors.toSet());
|
|
|
|
|
}
|
|
|
|
|
public final Login.Level level;
|
|
|
|
|
public final Set<String> existing;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 生成一份试卷。
|
|
|
|
|
*
|
|
|
|
|
* @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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Generator(Login.Level level, List<String> existingQuestions) {
|
|
|
|
|
this.level = level;
|
|
|
|
|
this.existing = existingQuestions.stream().map(String::trim).collect(Collectors.toSet());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (generated.size() < n) {
|
|
|
|
|
System.out.println(
|
|
|
|
|
"注意:无法生成足够的不重复题目,已生成 "
|
|
|
|
|
+ generated.size() + " 道题(请求 " + n + " 道)");
|
|
|
|
|
/**
|
|
|
|
|
* 生成一份试卷。
|
|
|
|
|
*
|
|
|
|
|
* @param n 需要生成的题目数量
|
|
|
|
|
* @return 生成的题目列表
|
|
|
|
|
*/
|
|
|
|
|
public 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);
|
|
|
|
|
}
|
|
|
|
|
return new ArrayList<>(generated);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//去空格
|
|
|
|
|
private String normalize(String s) {
|
|
|
|
|
return s.replaceAll("\\s+", "").toLowerCase();
|
|
|
|
|
if (generated.size() < n) {
|
|
|
|
|
System.out.println(
|
|
|
|
|
"注意:无法生成足够的不重复题目,已生成 "
|
|
|
|
|
+ generated.size() + " 道题(请求 " + n + " 道)");
|
|
|
|
|
}
|
|
|
|
|
return new ArrayList<>(generated);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 生成单题主逻辑
|
|
|
|
|
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);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
//去空格
|
|
|
|
|
public String normalize(String s) {
|
|
|
|
|
return s.replaceAll("\\s+", "").toLowerCase();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 生成单题主逻辑
|
|
|
|
|
public 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));
|
|
|
|
|
// 生成小学题(只有 + - * / 和括号)
|
|
|
|
|
public 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));
|
|
|
|
|
}
|
|
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
} 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();
|
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
// 生成初中题:至少包含一个 ^2 或 sqrt()
|
|
|
|
|
public 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;
|
|
|
|
|
}
|
|
|
|
|
// 生成高中题:至少包含 sin/cos/tan
|
|
|
|
|
public 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;
|
|
|
|
|
// 把表达式中某个数字替换为 (x)^2
|
|
|
|
|
public 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;
|
|
|
|
|
// 把表达式中某个数字替换为 sqrt(x)
|
|
|
|
|
public 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;
|
|
|
|
|
// 把某个数字或子表达式替换为 sin(x)/cos(x)/tan(x)
|
|
|
|
|
public 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++;
|
|
|
|
|
// 找出数字位置
|
|
|
|
|
public 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;
|
|
|
|
|
}
|
|
|
|
|
return spans;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//a..b随机数
|
|
|
|
|
private int randInt(int a, int b) {
|
|
|
|
|
return RAND.nextInt(b - a + 1) + a;
|
|
|
|
|
}
|
|
|
|
|
//a..b随机数
|
|
|
|
|
public 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()));
|
|
|
|
|
}
|
|
|
|
|
//抽签功能
|
|
|
|
|
public <T> T randomChoice(List<T> list) {
|
|
|
|
|
return list.get(RAND.nextInt(list.size()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|