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.
partner_project/src/util/ExpressionUtils.java

358 lines
14 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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)");
}
}