|
|
package com.coproject.service;
|
|
|
|
|
|
import java.text.DecimalFormat;
|
|
|
import java.util.*;
|
|
|
import java.util.regex.Matcher;
|
|
|
import java.util.regex.Pattern;
|
|
|
|
|
|
/**
|
|
|
* 统一题目引擎(单文件可移植版)
|
|
|
* - 封装小学/初中/高中三段题目生成逻辑
|
|
|
* - 保留与原项目一致的表达式与答案生成策略
|
|
|
* - 额外提供选择题与试卷生成功能(不依赖数据库)
|
|
|
*/
|
|
|
public class UnifiedQuestionEngine {
|
|
|
private final Random random = new Random();
|
|
|
private final DecimalFormat df = new DecimalFormat("#.##");
|
|
|
|
|
|
/** 根据难度生成一道题(与原项目接口保持一致) */
|
|
|
public MathProblem generateProblem(String difficulty) {
|
|
|
switch (difficulty) {
|
|
|
case "小学":
|
|
|
return new Elementary(random, df).generate();
|
|
|
case "初中":
|
|
|
return new Middle(random, df).generate();
|
|
|
case "高中":
|
|
|
return new High(random, df).generate();
|
|
|
default:
|
|
|
throw new IllegalArgumentException("不支持的难度级别: " + difficulty);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/** 根据难度生成一道选择题(四个选项,含正确答案) */
|
|
|
public ChoiceQuestion generateChoiceQuestion(String difficulty) {
|
|
|
MathProblem p = generateProblem(difficulty);
|
|
|
// 高中:题干保持三角函数形式,纯三角函数题生成符号化(根号)选项
|
|
|
if ("高中".equals(p.getDifficulty())) {
|
|
|
if (isSimpleTrigExpression(p.getExpression())) {
|
|
|
return toTrigChoice(p);
|
|
|
} else if (containsTrig(p.getExpression())) {
|
|
|
// 混合常数 + 三角项,构造符号化选项
|
|
|
return toMixedTrigSumChoice(p);
|
|
|
}
|
|
|
}
|
|
|
return toChoice(p);
|
|
|
}
|
|
|
|
|
|
/** 生成试卷:确保题干唯一(同一张卷子不重复),返回选择题列表 */
|
|
|
public List<ChoiceQuestion> generatePaper(String difficulty, int count) {
|
|
|
if (count <= 0) throw new IllegalArgumentException("题目数量必须为正数");
|
|
|
Set<String> seen = new HashSet<>();
|
|
|
List<ChoiceQuestion> paper = new ArrayList<>(count);
|
|
|
int attempts = 0, maxAttempts = count * 10;
|
|
|
while (paper.size() < count && attempts < maxAttempts) {
|
|
|
attempts++;
|
|
|
ChoiceQuestion q = generateChoiceQuestion(difficulty);
|
|
|
// 初中:不允许出现只有一个根号项的题干,如 "√121 = ?"
|
|
|
if ("初中".equals(difficulty)) {
|
|
|
String stem = q.getStem();
|
|
|
// 修复:Java 字符串中 ? 需要使用 \\? 转义,避免非法转义
|
|
|
if (stem != null && stem.matches("^√\\d+\\s*=\\s*\\?$")) {
|
|
|
continue; // 跳过,重新生成
|
|
|
}
|
|
|
}
|
|
|
if (seen.add(q.getStem())) {
|
|
|
paper.add(q);
|
|
|
}
|
|
|
}
|
|
|
if (paper.size() < count) {
|
|
|
throw new RuntimeException("在限定尝试次数内未能生成足够的不重复题目");
|
|
|
}
|
|
|
return paper;
|
|
|
}
|
|
|
|
|
|
// ---------- 选择题构造 ----------
|
|
|
private ChoiceQuestion toChoice(MathProblem p) {
|
|
|
String correct = p.getAnswer();
|
|
|
double correctVal = safeParseDouble(correct);
|
|
|
// 高中:纯三角函数题直接生成符号化选项,题干保持三角函数形式
|
|
|
if ("高中".equals(p.getDifficulty())) {
|
|
|
if (isSimpleTrigExpression(p.getExpression())) {
|
|
|
return toTrigChoice(p);
|
|
|
}
|
|
|
}
|
|
|
Set<String> optionsSet = new LinkedHashSet<>();
|
|
|
optionsSet.add(correct);
|
|
|
|
|
|
// 生成三个干扰项:围绕正确值的合理偏差与格式一致
|
|
|
int guard = 0;
|
|
|
if (isEffectivelyInteger(correctVal) && !"高中".equals(p.getDifficulty())) {
|
|
|
// 若答案为整数(且非高中三角函数情形),优先生成整数干扰项,避免不合理小数
|
|
|
int base = (int) Math.round(correctVal);
|
|
|
while (optionsSet.size() < 4 && guard++ < 100) {
|
|
|
int delta = pickIntegerDelta(base);
|
|
|
int candidate = base + delta;
|
|
|
String formatted = String.valueOf(candidate);
|
|
|
if (!formatted.equals(correct)) optionsSet.add(formatted);
|
|
|
}
|
|
|
} else {
|
|
|
while (optionsSet.size() < 4 && guard++ < 100) {
|
|
|
double delta = pickDelta(correctVal);
|
|
|
double candidate = correctVal + delta;
|
|
|
String formatted = df.format(candidate);
|
|
|
if (!formatted.equals(correct)) optionsSet.add(formatted);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 若仍不足,则退化为随机整数干扰项
|
|
|
while (optionsSet.size() < 4) {
|
|
|
optionsSet.add(String.valueOf(random.nextInt(200) + 1));
|
|
|
}
|
|
|
|
|
|
List<String> options = new ArrayList<>(optionsSet);
|
|
|
Collections.shuffle(options, random);
|
|
|
int correctIndex = options.indexOf(correct);
|
|
|
return new ChoiceQuestion(p.getExpression() + " = ?", options, correctIndex);
|
|
|
}
|
|
|
|
|
|
private ChoiceQuestion toTrigChoice(MathProblem p) {
|
|
|
String correctSymbolic = p.getAnswer(); // 已经是符号化形式,如 "√3/2"
|
|
|
Set<String> optionsSet = new LinkedHashSet<>();
|
|
|
optionsSet.add(correctSymbolic);
|
|
|
|
|
|
// 从常见特殊角值构造候选池
|
|
|
String[] commonSymbolic = {"0", "1/2", "√2/2", "√3/2", "1", "1/√3", "√3"};
|
|
|
for (String sym : commonSymbolic) {
|
|
|
if (!sym.equals(correctSymbolic)) {
|
|
|
optionsSet.add(sym);
|
|
|
}
|
|
|
if (optionsSet.size() >= 4) break;
|
|
|
}
|
|
|
|
|
|
// 若仍不足,添加一些变形
|
|
|
if (optionsSet.size() < 4) {
|
|
|
String[] extras = {"2", "-1/2", "2√3", "√6/2"};
|
|
|
for (String extra : extras) {
|
|
|
if (!optionsSet.contains(extra)) {
|
|
|
optionsSet.add(extra);
|
|
|
}
|
|
|
if (optionsSet.size() >= 4) break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
List<String> options = new ArrayList<>(optionsSet);
|
|
|
Collections.shuffle(options, random);
|
|
|
int correctIndex = options.indexOf(correctSymbolic);
|
|
|
return new ChoiceQuestion(p.getExpression() + " = ?", options, correctIndex);
|
|
|
}
|
|
|
|
|
|
private boolean isSimpleTrigExpression(String expr) {
|
|
|
return expr.matches("(sin|cos|tan)\\d+");
|
|
|
}
|
|
|
|
|
|
private boolean containsTrig(String expr) {
|
|
|
return expr.matches(".*(sin|cos|tan)\\d+.*");
|
|
|
}
|
|
|
|
|
|
private boolean isEffectivelyInteger(double val) {
|
|
|
return Math.abs(val - Math.round(val)) < 1e-9;
|
|
|
}
|
|
|
|
|
|
private int pickIntegerDelta(int base) {
|
|
|
int[] deltas = {-10, -5, -3, -2, -1, 1, 2, 3, 5, 10};
|
|
|
return deltas[random.nextInt(deltas.length)];
|
|
|
}
|
|
|
|
|
|
private double pickDelta(double base) {
|
|
|
double[] factors = {-0.3, -0.2, -0.1, -0.05, 0.05, 0.1, 0.2, 0.3};
|
|
|
double factor = factors[random.nextInt(factors.length)];
|
|
|
return base * factor;
|
|
|
}
|
|
|
|
|
|
private double safeParseDouble(String s) {
|
|
|
try {
|
|
|
return Double.parseDouble(s);
|
|
|
} catch (NumberFormatException e) {
|
|
|
return 0.0; // 符号字符串无法解析为数值,返回默认值
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 供外层方法(如 toMixedTrigSumChoice)使用的简单运算符优先级计算
|
|
|
private static double evalWithPrecedence(double[] operands, String[] operators) {
|
|
|
if (operands.length == 1) return operands[0];
|
|
|
List<Double> vals = new ArrayList<>();
|
|
|
for (double v : operands) vals.add(v);
|
|
|
List<String> ops = new ArrayList<>(Arrays.asList(operators));
|
|
|
for (int i = 0; i < ops.size(); i++) {
|
|
|
if ("*".equals(ops.get(i)) || "/".equals(ops.get(i))) {
|
|
|
double a = vals.get(i), b = vals.get(i + 1);
|
|
|
double r = "/".equals(ops.get(i)) ? (b != 0 ? a / b : a) : a * b;
|
|
|
vals.set(i, r); vals.remove(i + 1); ops.remove(i); i--;
|
|
|
}
|
|
|
}
|
|
|
for (int i = 0; i < ops.size(); i++) {
|
|
|
double a = vals.get(i), b = vals.get(i + 1);
|
|
|
String op = ops.get(i);
|
|
|
double r = "+".equals(op) ? a + b : a - b;
|
|
|
vals.set(i, r); vals.remove(i + 1); ops.remove(i); i--;
|
|
|
}
|
|
|
return vals.get(0);
|
|
|
}
|
|
|
|
|
|
// ---------- 三角函数符号化 ----------
|
|
|
private static String getSymbolicTrig(String function, int angle) {
|
|
|
String key = function + angle;
|
|
|
switch (key) {
|
|
|
case "sin0": case "cos90": case "tan0": return "0";
|
|
|
case "sin30": case "cos60": return "1/2";
|
|
|
case "sin45": case "cos45": return "√2/2";
|
|
|
case "sin60": case "cos30": return "√3/2";
|
|
|
case "sin90": case "cos0": return "1";
|
|
|
case "tan30": return "1/√3";
|
|
|
case "tan45": return "1";
|
|
|
case "tan60": return "√3";
|
|
|
default: return "0"; // 兜底
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// ---------- 数据类 ----------
|
|
|
public static class MathProblem {
|
|
|
private final String expression, answer, difficulty;
|
|
|
public MathProblem(String expression, String answer, String difficulty) {
|
|
|
this.expression = expression; this.answer = answer; this.difficulty = difficulty;
|
|
|
}
|
|
|
public String getExpression() { return expression; }
|
|
|
public String getAnswer() { return answer; }
|
|
|
public String getDifficulty() { return difficulty; }
|
|
|
}
|
|
|
|
|
|
public static class ChoiceQuestion {
|
|
|
private final String stem;
|
|
|
private final List<String> options;
|
|
|
private final int correctIndex;
|
|
|
public ChoiceQuestion(String stem, List<String> options, int correctIndex) {
|
|
|
this.stem = stem; this.options = options; this.correctIndex = correctIndex;
|
|
|
}
|
|
|
public String getStem() { return stem; }
|
|
|
public List<String> getOptions() { return options; }
|
|
|
public int getCorrectIndex() { return correctIndex; }
|
|
|
}
|
|
|
|
|
|
// ---------- 题目生成器基类 ----------
|
|
|
abstract static class Base {
|
|
|
protected final Random random;
|
|
|
protected final DecimalFormat df;
|
|
|
public Base(Random random, DecimalFormat df) { this.random = random; this.df = df; }
|
|
|
public abstract MathProblem generate();
|
|
|
|
|
|
protected double applyOperatorDouble(double a, double b, String op) {
|
|
|
switch (op) {
|
|
|
case "+": return a + b;
|
|
|
case "-": return a - b;
|
|
|
case "*": return a * b;
|
|
|
case "/": return b != 0 ? a / b : a;
|
|
|
default: return a;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
protected double evaluateWithPrecedenceDouble(double[] operands, String[] operators) {
|
|
|
if (operands.length == 1) return operands[0];
|
|
|
List<Double> vals = new ArrayList<>(); for (double v : operands) vals.add(v);
|
|
|
List<String> ops = new ArrayList<>(Arrays.asList(operators));
|
|
|
for (int i = 0; i < ops.size(); i++) {
|
|
|
if ("*".equals(ops.get(i)) || "/".equals(ops.get(i))) {
|
|
|
double result = applyOperatorDouble(vals.get(i), vals.get(i + 1), ops.get(i));
|
|
|
vals.set(i, result); vals.remove(i + 1); ops.remove(i); i--;
|
|
|
}
|
|
|
}
|
|
|
for (int i = 0; i < ops.size(); i++) {
|
|
|
double result = applyOperatorDouble(vals.get(i), vals.get(i + 1), ops.get(i));
|
|
|
vals.set(i, result); vals.remove(i + 1); ops.remove(i); i--;
|
|
|
}
|
|
|
return vals.get(0);
|
|
|
}
|
|
|
|
|
|
protected int findBracketStart(double[] operands, String[] operators, double probability) {
|
|
|
if (random.nextDouble() > probability || operands.length < 3) return -1;
|
|
|
return random.nextInt(operands.length - 1);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// ---------- 小学题目生成器 ----------
|
|
|
static class Elementary extends Base {
|
|
|
public Elementary(Random random, DecimalFormat df) { super(random, df); }
|
|
|
@Override public MathProblem generate() {
|
|
|
int operandCount = random.nextInt(3) + 3; // 3-5
|
|
|
int[] operands = new int[operandCount];
|
|
|
String[] operators = new String[operandCount - 1];
|
|
|
for (int i = 0; i < operandCount; i++) operands[i] = random.nextInt(100) + 1;
|
|
|
String[] ops = {"+", "-", "*"};
|
|
|
for (int i = 0; i < operandCount - 1; i++) operators[i] = ops[random.nextInt(ops.length)];
|
|
|
StringBuilder expr = new StringBuilder();
|
|
|
for (int i = 0; i < operandCount; i++) {
|
|
|
expr.append(operands[i]);
|
|
|
if (i < operandCount - 1) expr.append(" ").append(operators[i]).append(" ");
|
|
|
}
|
|
|
double[] vals = new double[operandCount]; for (int i = 0; i < operandCount; i++) vals[i] = operands[i];
|
|
|
double result = evaluateWithPrecedenceDouble(vals, operators);
|
|
|
return new MathProblem(expr.toString(), df.format(result), "小学");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// ---------- 初中题目生成器 ----------
|
|
|
static class Middle extends Base {
|
|
|
public Middle(Random random, DecimalFormat df) { super(random, df); }
|
|
|
@Override public MathProblem generate() {
|
|
|
// 始终生成 3-5 个操作数;当包含根号时,根号为其中一个或两个项
|
|
|
int operandCount = random.nextInt(3) + 3; // 3-5
|
|
|
|
|
|
boolean includeSqrt = random.nextDouble() < 0.5; // 约半数题含根号
|
|
|
int sqrtCount = includeSqrt ? (1 + random.nextInt(2)) : 0; // 1-2 个根号项
|
|
|
if (sqrtCount > operandCount) sqrtCount = operandCount; // 不超过操作数数量
|
|
|
|
|
|
double[] operands = new double[operandCount];
|
|
|
String[] operators = new String[operandCount - 1];
|
|
|
String[] termTexts = new String[operandCount];
|
|
|
|
|
|
// 随机选择根号项位置
|
|
|
Set<Integer> sqrtPos = new HashSet<>();
|
|
|
while (sqrtPos.size() < sqrtCount) {
|
|
|
sqrtPos.add(random.nextInt(operandCount));
|
|
|
}
|
|
|
|
|
|
// 构造各项
|
|
|
for (int i = 0; i < operandCount; i++) {
|
|
|
if (sqrtPos.contains(i)) {
|
|
|
// 使用完全平方数,确保计算结果为整数,便于初中题目
|
|
|
int base = random.nextInt(9) + 2; // 2-10,确保 √n 的 n ≤ 100
|
|
|
int square = base * base;
|
|
|
operands[i] = Math.sqrt(square); // = base
|
|
|
termTexts[i] = "√" + square;
|
|
|
} else {
|
|
|
int val = random.nextInt(100) + 1; // 1-100 的常数
|
|
|
operands[i] = val;
|
|
|
termTexts[i] = String.valueOf(val);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 构造运算符(保持简单四则)
|
|
|
String[] ops = {"+", "-", "*", "/"};
|
|
|
for (int i = 0; i < operandCount - 1; i++) {
|
|
|
operators[i] = ops[random.nextInt(ops.length)];
|
|
|
}
|
|
|
|
|
|
// 拼接表达式文本
|
|
|
StringBuilder expr = new StringBuilder();
|
|
|
for (int i = 0; i < operandCount; i++) {
|
|
|
expr.append(termTexts[i]);
|
|
|
if (i < operandCount - 1) expr.append(" ").append(operators[i]).append(" ");
|
|
|
}
|
|
|
|
|
|
double result = evaluateWithPrecedenceDouble(operands, operators);
|
|
|
return new MathProblem(expr.toString(), df.format(result), "初中");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// ---------- 高中题目生成器 ----------
|
|
|
static class High extends Base {
|
|
|
public High(Random random, DecimalFormat df) { super(random, df); }
|
|
|
@Override public MathProblem generate() {
|
|
|
// 生成包含三角函数与常数混合运算的表达式(3-5个操作数,至少包含一个三角函数)
|
|
|
int operandCount = random.nextInt(3) + 3; // 3-5
|
|
|
int trigIndex = random.nextInt(operandCount);
|
|
|
|
|
|
double[] operands = new double[operandCount];
|
|
|
String[] operators = new String[operandCount - 1];
|
|
|
String[] termTexts = new String[operandCount];
|
|
|
|
|
|
// 构造操作数
|
|
|
for (int i = 0; i < operandCount; i++) {
|
|
|
if (i == trigIndex) {
|
|
|
String[] functions = {"sin", "cos", "tan"};
|
|
|
String func = functions[random.nextInt(functions.length)];
|
|
|
int[] angles = {0, 30, 45, 60, 90};
|
|
|
int angle = angles[random.nextInt(angles.length)];
|
|
|
if ("tan".equals(func) && angle == 90) angle = 60; // 避免未定义
|
|
|
operands[i] = calculateTrigFunction(func, angle);
|
|
|
termTexts[i] = func + angle;
|
|
|
} else {
|
|
|
int val = random.nextInt(100) + 1; // 1-100 的常数
|
|
|
operands[i] = val;
|
|
|
termTexts[i] = String.valueOf(val);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 构造运算符(+ - * /)
|
|
|
String[] ops = {"+", "-", "*", "/"};
|
|
|
for (int i = 0; i < operandCount - 1; i++) {
|
|
|
operators[i] = ops[random.nextInt(ops.length)];
|
|
|
}
|
|
|
|
|
|
// 为了让选项可符号化展示,约束三角项相邻运算为加/减
|
|
|
String[] addOps = {"+", "-"};
|
|
|
if (trigIndex > 0) {
|
|
|
operators[trigIndex - 1] = addOps[random.nextInt(addOps.length)];
|
|
|
}
|
|
|
if (trigIndex < operandCount - 1) {
|
|
|
operators[trigIndex] = addOps[random.nextInt(addOps.length)];
|
|
|
}
|
|
|
|
|
|
// 拼接表达式文本
|
|
|
StringBuilder expr = new StringBuilder();
|
|
|
for (int i = 0; i < operandCount; i++) {
|
|
|
expr.append(termTexts[i]);
|
|
|
if (i < operandCount - 1) expr.append(" ").append(operators[i]).append(" ");
|
|
|
}
|
|
|
|
|
|
// 计算结果(运算符优先级)
|
|
|
double result = evaluateWithPrecedenceDouble(operands, operators);
|
|
|
return new MathProblem(expr.toString(), df.format(result), "高中");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 混合三角表达式(包含常数与一个三角项)选项符号化
|
|
|
private ChoiceQuestion toMixedTrigSumChoice(MathProblem p) {
|
|
|
String expr = p.getExpression();
|
|
|
String[] tokens = expr.split(" ");
|
|
|
|
|
|
// 解析操作数与运算符
|
|
|
List<String> ops = new ArrayList<>();
|
|
|
List<Double> vals = new ArrayList<>();
|
|
|
int trigIdx = -1; String trigFunc = null; int trigAngle = 0;
|
|
|
for (int i = 0; i < tokens.length; i++) {
|
|
|
if (i % 2 == 0) { // 操作数
|
|
|
String tk = tokens[i];
|
|
|
if (tk.matches("(sin|cos|tan)\\d+")) {
|
|
|
trigIdx = vals.size();
|
|
|
trigFunc = tk.substring(0, 3);
|
|
|
trigAngle = Integer.parseInt(tk.substring(3));
|
|
|
vals.add(calculateTrigFunction(trigFunc, trigAngle));
|
|
|
} else {
|
|
|
vals.add(Double.parseDouble(tk));
|
|
|
}
|
|
|
} else { // 运算符
|
|
|
ops.add(tokens[i]);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 计算仅常数部分(将三角项替换为0)
|
|
|
double[] consts = new double[vals.size()];
|
|
|
for (int i = 0; i < vals.size(); i++) consts[i] = (i == trigIdx) ? 0.0 : vals.get(i);
|
|
|
String[] opArr = ops.toArray(new String[0]);
|
|
|
double K = evalWithPrecedence(consts, opArr);
|
|
|
|
|
|
// 确定与三角项相连的加减符号(左侧优先)
|
|
|
String signOp;
|
|
|
if (trigIdx > 0) signOp = ops.get(trigIdx - 1);
|
|
|
else if (!ops.isEmpty()) signOp = ops.get(0);
|
|
|
else signOp = "+";
|
|
|
String signChar = "+".equals(signOp) ? "+" : "-".equals(signOp) ? "-" : "+";
|
|
|
|
|
|
// 三角项符号化与数值
|
|
|
String trigSym = getSymbolicTrig(trigFunc, trigAngle);
|
|
|
double trigVal = calculateTrigFunction(trigFunc, trigAngle);
|
|
|
String kText = isEffectivelyInteger(K) ? String.valueOf((int)Math.round(K)) : df.format(K);
|
|
|
|
|
|
// 决策:若三角值为可有理(不含“√”),则合并为数值;若为无理数(含“√”),保持符号形式
|
|
|
boolean trigIsIrrational = trigSym.contains("√");
|
|
|
|
|
|
// 构造正确答案
|
|
|
String correct;
|
|
|
if (trigIsIrrational) {
|
|
|
correct = kText + " " + signChar + " " + trigSym;
|
|
|
} else {
|
|
|
double combined = "+".equals(signChar) ? (K + trigVal) : (K - trigVal);
|
|
|
correct = isEffectivelyInteger(combined) ? String.valueOf((int)Math.round(combined)) : df.format(combined);
|
|
|
}
|
|
|
|
|
|
// 干扰项:变化 K、变化三角值、变化符号
|
|
|
Set<String> set = new LinkedHashSet<>();
|
|
|
set.add(correct);
|
|
|
|
|
|
// 1) 变化K
|
|
|
int guard = 0;
|
|
|
while (set.size() < 3 && guard++ < 50) {
|
|
|
int delta = pickIntegerDelta((int)Math.round(K));
|
|
|
double kCand = isEffectivelyInteger(K) ? (int)Math.round(K) + delta : K + delta;
|
|
|
String cand;
|
|
|
if (trigIsIrrational) {
|
|
|
String kCandText = isEffectivelyInteger(kCand) ? String.valueOf((int)Math.round(kCand)) : df.format(kCand);
|
|
|
cand = kCandText + " " + signChar + " " + trigSym;
|
|
|
} else {
|
|
|
double combined = "+".equals(signChar) ? (kCand + trigVal) : (kCand - trigVal);
|
|
|
cand = isEffectivelyInteger(combined) ? String.valueOf((int)Math.round(combined)) : df.format(combined);
|
|
|
}
|
|
|
set.add(cand);
|
|
|
}
|
|
|
|
|
|
// 2) 变化三角值
|
|
|
String[] symPool = {"0", "1/2", "√2/2", "√3/2", "1", "1/√3", "√3"};
|
|
|
for (String s : symPool) {
|
|
|
if (set.size() >= 4) break;
|
|
|
if (s.equals(trigSym)) continue;
|
|
|
boolean sIrrational = s.contains("√");
|
|
|
String cand;
|
|
|
if (trigIsIrrational || sIrrational) {
|
|
|
// 若当前或候选三角值是无理数,保持符号形式
|
|
|
cand = kText + " " + signChar + " " + s;
|
|
|
} else {
|
|
|
// 有理数:合并为数值
|
|
|
double sVal = parseSymbolicToDouble(s);
|
|
|
double combined = "+".equals(signChar) ? (K + sVal) : (K - sVal);
|
|
|
cand = isEffectivelyInteger(combined) ? String.valueOf((int)Math.round(combined)) : df.format(combined);
|
|
|
}
|
|
|
set.add(cand);
|
|
|
}
|
|
|
|
|
|
// 3) 变化符号
|
|
|
if (set.size() < 4) {
|
|
|
String otherSign = "+".equals(signChar) ? "-" : "+";
|
|
|
String cand;
|
|
|
if (trigIsIrrational) {
|
|
|
cand = kText + " " + otherSign + " " + trigSym;
|
|
|
} else {
|
|
|
double combined = "+".equals(otherSign) ? (K + trigVal) : (K - trigVal);
|
|
|
cand = isEffectivelyInteger(combined) ? String.valueOf((int)Math.round(combined)) : df.format(combined);
|
|
|
}
|
|
|
set.add(cand);
|
|
|
}
|
|
|
|
|
|
List<String> options = new ArrayList<>(set);
|
|
|
Collections.shuffle(options, random);
|
|
|
int correctIndex = options.indexOf(correct);
|
|
|
return new ChoiceQuestion(p.getExpression() + " = ?", options, correctIndex);
|
|
|
}
|
|
|
|
|
|
// 解析常见符号三角值为数值(仅用于没有根号的情况)
|
|
|
private double parseSymbolicToDouble(String s) {
|
|
|
switch (s) {
|
|
|
case "0": return 0.0;
|
|
|
case "1/2": return 0.5;
|
|
|
case "1": return 1.0;
|
|
|
default:
|
|
|
// 兜底:含根号的情况不会走到这里;其它未知返回0
|
|
|
return 0.0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 供外层与静态内部类调用的三角函数计算方法(与 Base 内实现保持一致)
|
|
|
private static double calculateTrigFunction(String function, int angle) {
|
|
|
double radians = Math.toRadians(angle);
|
|
|
switch (function) {
|
|
|
case "sin": return Math.sin(radians);
|
|
|
case "cos": return Math.cos(radians);
|
|
|
case "tan": return Math.tan(radians);
|
|
|
default: return 0.0;
|
|
|
}
|
|
|
}
|
|
|
} |