|
|
package util;
|
|
|
|
|
|
import generator.Problem;
|
|
|
|
|
|
import java.util.*;
|
|
|
|
|
|
public class ExpressionUtils {
|
|
|
private static Random rand = new Random();
|
|
|
|
|
|
private static final String[] OPS_PRIMARY = {"+", "-", "*", "/"};
|
|
|
private static final String[] OPS_MIDDLE = {"+", "-", "*", "/"};
|
|
|
//private static final String[] OPS_HIGH = {"+", "-", "*", "/", "^2", "sqrt"};
|
|
|
|
|
|
|
|
|
public static String randomNumber() {
|
|
|
return String.valueOf(rand.nextInt(100) + 1);
|
|
|
}
|
|
|
|
|
|
// --- 表达式求解和选项生成辅助方法 ---
|
|
|
|
|
|
public static double solveExpression(String expr) {
|
|
|
// 预处理表达式,确保格式正确
|
|
|
String parsableExpr = expr
|
|
|
.replaceAll("(\\d+|\\([^)]+\\))\\^2", "pow($1, 2)") // 处理平方
|
|
|
.replaceAll("sqrt\\s+(\\d+)", "sqrt($1)") // 处理缺少括号的sqrt
|
|
|
.replaceAll("\\s+", " "); // 标准化空格
|
|
|
|
|
|
try {
|
|
|
return new Object() {
|
|
|
int pos = -1, ch;
|
|
|
|
|
|
void nextChar() {
|
|
|
ch = (++pos < parsableExpr.length()) ? parsableExpr.charAt(pos) : -1;
|
|
|
}
|
|
|
|
|
|
boolean eat(int charToEat) {
|
|
|
while (ch == ' ') nextChar();
|
|
|
if (ch == charToEat) {
|
|
|
nextChar();
|
|
|
return true;
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
double parse() {
|
|
|
nextChar();
|
|
|
double x = parseExpression();
|
|
|
if (pos < parsableExpr.length()) throw new RuntimeException("Unexpected: " + (char) ch);
|
|
|
return x;
|
|
|
}
|
|
|
|
|
|
double parseExpression() {
|
|
|
double x = parseTerm();
|
|
|
for (; ; ) {
|
|
|
if (eat('+')) x += parseTerm(); // addition
|
|
|
else if (eat('-')) x -= parseTerm(); // subtraction
|
|
|
else return x;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
double parseTerm() {
|
|
|
double x = parseFactor();
|
|
|
for (; ; ) {
|
|
|
if (eat('*')) x *= parseFactor(); // multiplication
|
|
|
else if (eat('/')) { // division
|
|
|
double divisor = parseFactor();
|
|
|
if (divisor == 0) throw new ArithmeticException("Division by zero");
|
|
|
x /= divisor;
|
|
|
} else return x;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
double parseFactor() {
|
|
|
if (eat('+')) return parseFactor(); // unary plus
|
|
|
if (eat('-')) return -parseFactor(); // unary minus
|
|
|
|
|
|
double x;
|
|
|
int startPos = this.pos;
|
|
|
if (eat('(')) { // parentheses
|
|
|
x = parseExpression();
|
|
|
eat(')');
|
|
|
} else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers
|
|
|
while ((ch >= '0' && ch <= '9') || ch == '.') nextChar();
|
|
|
x = Double.parseDouble(parsableExpr.substring(startPos, this.pos));
|
|
|
} else if (ch >= 'a' && ch <= 'z') { // functions
|
|
|
while (ch >= 'a' && ch <= 'z') nextChar();
|
|
|
String func = parsableExpr.substring(startPos, this.pos);
|
|
|
x = parseFactor();
|
|
|
switch (func) {
|
|
|
case "sqrt":
|
|
|
x = Math.sqrt(x);
|
|
|
break;
|
|
|
case "sin":
|
|
|
x = Math.sin(Math.toRadians(x));
|
|
|
break;
|
|
|
case "cos":
|
|
|
x = Math.cos(Math.toRadians(x));
|
|
|
break;
|
|
|
case "tan":
|
|
|
x = Math.tan(Math.toRadians(x));
|
|
|
break;
|
|
|
case "pow":
|
|
|
eat(',');
|
|
|
double y = parseExpression();
|
|
|
x = Math.pow(x, y);
|
|
|
eat(')');
|
|
|
break;
|
|
|
default:
|
|
|
throw new RuntimeException("Unknown function: " + func);
|
|
|
}
|
|
|
} else {
|
|
|
throw new RuntimeException("Unexpected: " + (char) ch);
|
|
|
}
|
|
|
|
|
|
return x;
|
|
|
}
|
|
|
}.parse();
|
|
|
} catch (Exception e) {
|
|
|
System.err.println("Error parsing expression: '" + expr + "'. Parsable version: '" + parsableExpr + "'. Error: " + e.getMessage());
|
|
|
return Double.NaN;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
private static String formatResult(double result) {
|
|
|
if (Double.isNaN(result)) {
|
|
|
return "Error";
|
|
|
}
|
|
|
if (Math.abs(result - Math.round(result)) < 0.0001) {
|
|
|
return String.valueOf((int) Math.round(result));
|
|
|
}
|
|
|
return String.format("%.2f", result);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* MODIFICATION: 确保为整数答案生成整数干扰项
|
|
|
*/
|
|
|
private static String generateDistractor(double correctResult, List<String> existingOptions) {
|
|
|
if (Double.isNaN(correctResult)) {
|
|
|
return String.valueOf(rand.nextInt(100));
|
|
|
}
|
|
|
|
|
|
// 检查正确答案是否为整数
|
|
|
boolean isIntegerResult = Math.abs(correctResult - Math.round(correctResult)) < 0.0001;
|
|
|
|
|
|
double distractor;
|
|
|
String distractorStr;
|
|
|
|
|
|
do {
|
|
|
if (isIntegerResult) {
|
|
|
// 生成一个整数干扰项
|
|
|
int offset = rand.nextInt(20) + 1; // 扩大随机范围以避免重复
|
|
|
distractor = rand.nextBoolean() ? Math.round(correctResult) + offset : Math.round(correctResult) - offset;
|
|
|
// 确保干扰项不为负数
|
|
|
if (distractor < 0) {
|
|
|
distractor = Math.round(correctResult) + offset;
|
|
|
}
|
|
|
} else {
|
|
|
// 答案是小数,则生成小数干扰项
|
|
|
distractor = correctResult +
|
|
|
(rand.nextInt(10) + 1) * (rand.nextBoolean() ? 1 : -1) +
|
|
|
(rand.nextDouble() * 0.9);
|
|
|
}
|
|
|
distractorStr = formatResult(distractor);
|
|
|
} while (existingOptions.contains(distractorStr)); // 确保选项不重复
|
|
|
return distractorStr;
|
|
|
}
|
|
|
|
|
|
|
|
|
public static List<String> generateOptions(double correctResult) {
|
|
|
List<String> options = new ArrayList<>();
|
|
|
options.add(formatResult(correctResult));
|
|
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
|
options.add(generateDistractor(correctResult, options));
|
|
|
}
|
|
|
|
|
|
Collections.shuffle(options);
|
|
|
return options;
|
|
|
}
|
|
|
|
|
|
public static Problem createProblem(String expression) {
|
|
|
double result = solveExpression(expression);
|
|
|
|
|
|
// 如果解析失败,返回null,让调用者重新生成
|
|
|
if (Double.isNaN(result)) {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
String correctAnswerOption = formatResult(result);
|
|
|
List<String> options = generateOptions(result);
|
|
|
return new Problem(expression, result, options, correctAnswerOption);
|
|
|
}
|
|
|
|
|
|
// --- 题目生成方法 ---
|
|
|
|
|
|
private static int getDivisor(int dividend) {
|
|
|
List<Integer> divisors = new ArrayList<>();
|
|
|
for (int j = 1; j <= dividend; j++) {
|
|
|
if (dividend % j == 0) {
|
|
|
divisors.add(j);
|
|
|
}
|
|
|
}
|
|
|
return divisors.get(rand.nextInt(divisors.size()));
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* MODIFICATION: 增加答案非负、括号数量和操作数关联的逻辑
|
|
|
*/
|
|
|
public static Problem generatePrimaryExpr() {
|
|
|
Problem problem;
|
|
|
double result;
|
|
|
|
|
|
do {
|
|
|
// 生成 2 到 4 个操作数
|
|
|
int operands = rand.nextInt(3) + 2;
|
|
|
List<String> parts = new ArrayList<>();
|
|
|
|
|
|
for (int i = 0; i < operands; i++) {
|
|
|
if (i > 0) {
|
|
|
parts.add(OPS_PRIMARY[rand.nextInt(OPS_PRIMARY.length)]);
|
|
|
}
|
|
|
|
|
|
// 仅当操作数 >= 3 时,才有可能生成括号
|
|
|
if (operands >= 3 && rand.nextBoolean() && i < operands - 1) {
|
|
|
int num1 = rand.nextInt(50) + 1;
|
|
|
String innerOp = OPS_PRIMARY[rand.nextInt(OPS_PRIMARY.length)];
|
|
|
int num2;
|
|
|
if (innerOp.equals("/")) {
|
|
|
num2 = getDivisor(num1);
|
|
|
} else {
|
|
|
num2 = rand.nextInt(50) + 1;
|
|
|
}
|
|
|
parts.add("(" + num1 + " " + innerOp + " " + num2 + ")");
|
|
|
i++; // 括号表达式计为2个操作数
|
|
|
} else {
|
|
|
int num;
|
|
|
if (i > 0 && parts.get(parts.size() - 1).equals("/")) {
|
|
|
String dividendStr = parts.get(parts.size() - 2);
|
|
|
if (dividendStr.startsWith("(")) {
|
|
|
// 为避免 (a+b)/c 这种复杂情况无法保证整数结果,直接替换运算符
|
|
|
parts.set(parts.size() - 1, OPS_PRIMARY[rand.nextInt(3)]); // +, -, *
|
|
|
num = rand.nextInt(100) + 1;
|
|
|
} else {
|
|
|
int dividend = Integer.parseInt(dividendStr);
|
|
|
num = getDivisor(dividend);
|
|
|
}
|
|
|
} else {
|
|
|
num = rand.nextInt(100) + 1;
|
|
|
}
|
|
|
parts.add(String.valueOf(num));
|
|
|
}
|
|
|
}
|
|
|
String expression = String.join(" ", parts);
|
|
|
problem = createProblem(expression);
|
|
|
result = problem.getResult();
|
|
|
// 循环直到答案为非负整数
|
|
|
} while (result < 0 || Double.isNaN(result) || Math.abs(result - Math.round(result)) > 0.0001);
|
|
|
|
|
|
return problem;
|
|
|
}
|
|
|
|
|
|
|
|
|
public static Problem generateMiddleExpr() {
|
|
|
int operands = rand.nextInt(5) + 1;
|
|
|
StringBuilder expr = new StringBuilder();
|
|
|
boolean hasSquareOrSqrt = false;
|
|
|
|
|
|
for (int i = 0; i < operands; i++) {
|
|
|
if (i > 0) {
|
|
|
expr.append(" ").append(OPS_MIDDLE[rand.nextInt(OPS_MIDDLE.length)]).append(" ");
|
|
|
}
|
|
|
int num = rand.nextInt(100) + 1;
|
|
|
if (!hasSquareOrSqrt && rand.nextBoolean()) {
|
|
|
expr.append(rand.nextBoolean() ? "sqrt(" + num + ")" : num + "^2");
|
|
|
hasSquareOrSqrt = true;
|
|
|
} else {
|
|
|
expr.append(num);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (!hasSquareOrSqrt) {
|
|
|
expr.append(" + ").append(rand.nextInt(50) + 1).append("^2");
|
|
|
}
|
|
|
|
|
|
return createProblem(expr.toString());
|
|
|
}
|
|
|
|
|
|
public static Problem generateHighExpr() {
|
|
|
int attempts = 0;
|
|
|
final int maxAttempts = 10;
|
|
|
|
|
|
while (attempts < maxAttempts) {
|
|
|
int operands = rand.nextInt(3) + 2; // 2-4个操作数
|
|
|
StringBuilder expr = new StringBuilder();
|
|
|
boolean hasTrig = false;
|
|
|
|
|
|
for (int i = 0; i < operands; i++) {
|
|
|
if (i > 0) {
|
|
|
String[] validOps = {"+", "-", "*", "/"};
|
|
|
String op = validOps[rand.nextInt(validOps.length)];
|
|
|
expr.append(" ").append(op).append(" ");
|
|
|
}
|
|
|
|
|
|
// 强制至少有一个操作数是三角函数
|
|
|
if (!hasTrig && (i == operands - 1 || rand.nextBoolean())) {
|
|
|
String[] funcs = {"sin", "cos", "tan"};
|
|
|
String func = funcs[rand.nextInt(funcs.length)];
|
|
|
int angle = rand.nextInt(90) + 1; // 1-90度
|
|
|
expr.append(func).append("(").append(angle).append(")");
|
|
|
hasTrig = true;
|
|
|
} else {
|
|
|
// 其他操作数可以是普通数字、平方或开方
|
|
|
int num = rand.nextInt(100) + 1;
|
|
|
if (rand.nextBoolean() && hasTrig) { // 确保已经有三角函数后再添加其他函数
|
|
|
if (rand.nextBoolean()) {
|
|
|
expr.append(num).append("^2");
|
|
|
} else {
|
|
|
expr.append("sqrt(").append(num).append(")");
|
|
|
}
|
|
|
} else {
|
|
|
expr.append(num);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 如果没有三角函数,强制添加一个
|
|
|
if (!hasTrig) {
|
|
|
String[] funcs = {"sin", "cos", "tan"};
|
|
|
String func = funcs[rand.nextInt(funcs.length)];
|
|
|
int angle = rand.nextInt(90) + 1;
|
|
|
|
|
|
if (rand.nextBoolean()) {
|
|
|
// 在开头添加
|
|
|
expr.insert(0, func + "(" + angle + ") + ");
|
|
|
} else {
|
|
|
// 在结尾添加
|
|
|
expr.append(" + ").append(func).append("(").append(angle).append(")");
|
|
|
}
|
|
|
hasTrig = true;
|
|
|
}
|
|
|
|
|
|
String expression = expr.toString();
|
|
|
|
|
|
// 验证表达式
|
|
|
Problem problem = createProblem(expression);
|
|
|
if (problem != null) {
|
|
|
return problem;
|
|
|
}
|
|
|
|
|
|
attempts++;
|
|
|
System.err.println("生成高中题目失败,尝试次数: " + attempts + ", 表达式: " + expression);
|
|
|
}
|
|
|
|
|
|
return createProblem("sin(30) + cos(60)");
|
|
|
}
|
|
|
|
|
|
} |