|
|
package com.ybw.mathapp.service;
|
|
|
|
|
|
import java.util.*;
|
|
|
|
|
|
/**
|
|
|
* 扩展的计算类,支持基础四则运算、括号、平方、开根号、三角函数 (sin, cos, tan)。
|
|
|
* 括号优先级最高。
|
|
|
* 高级运算符 (平方, 开根号, sin, cos, tan) 优先级高于四则运算。
|
|
|
* 平方是后置运算符,开根号和三角函数是前置运算符。
|
|
|
* 例如: "3 + 开根号 16 平方" 解释为 "3 + (sqrt(16))^2" = "3 + 4^2" = "3 + 16" = 19
|
|
|
* "开根号 ( 4 + 5 ) 平方" 解释为 "(sqrt(4+5))^2" = "sqrt(9)^2" = "3^2" = 9
|
|
|
* "sin 30 + 5" 解释为 "sin(30) + 5" (假设30是度数)
|
|
|
* 修正:在计算开根号时,若操作数为负数,则抛出 ArithmeticException。
|
|
|
*/
|
|
|
public class AdvancedCaculate {
|
|
|
|
|
|
private static final Set<String> OPERATORS = new HashSet<>(Arrays.asList("+", "-", "*", "/"));
|
|
|
private static final Set<String> ADVANCED_OPERATORS = new HashSet<>(Arrays.asList("平方", "开根号", "sin", "cos", "tan"));
|
|
|
private static final Map<String, Integer> PRECEDENCE = new HashMap<>();
|
|
|
|
|
|
static {
|
|
|
PRECEDENCE.put("+", 1);
|
|
|
PRECEDENCE.put("-", 1);
|
|
|
PRECEDENCE.put("*", 2);
|
|
|
PRECEDENCE.put("/", 2);
|
|
|
// 高级运算符优先级更高
|
|
|
PRECEDENCE.put("平方", 3); // 后置
|
|
|
PRECEDENCE.put("开根号", 3); // 前置
|
|
|
PRECEDENCE.put("sin", 3); // 前置
|
|
|
PRECEDENCE.put("cos", 3); // 前置
|
|
|
PRECEDENCE.put("tan", 3); // 前置
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 计算给定表达式的值。
|
|
|
*
|
|
|
* @param expressionTokens 表达式分词列表,例如 ["3", "+", "开根号", "16", "平方"]
|
|
|
* @return 计算结果
|
|
|
* @throws IllegalArgumentException 如果表达式无效
|
|
|
* @throws ArithmeticException 如果计算过程中出现错误(如除零、负数开根号)
|
|
|
*/
|
|
|
public static double calculate(List<String> expressionTokens) {
|
|
|
Stack<Double> numberStack = new Stack<>();
|
|
|
Stack<String> operatorStack = new Stack<>();
|
|
|
|
|
|
for (int i = 0; i < expressionTokens.size(); i++) {
|
|
|
String token = expressionTokens.get(i);
|
|
|
|
|
|
if (isNumeric(token)) {
|
|
|
numberStack.push(Double.parseDouble(token));
|
|
|
} else if (token.equals("(")) {
|
|
|
operatorStack.push(token);
|
|
|
} else if (token.equals(")")) {
|
|
|
while (!operatorStack.isEmpty() && !operatorStack.peek().equals("(")) {
|
|
|
processOperator(numberStack, operatorStack.pop());
|
|
|
}
|
|
|
if (!operatorStack.isEmpty()) { // Pop the "("
|
|
|
operatorStack.pop();
|
|
|
}
|
|
|
} else if (ADVANCED_OPERATORS.contains(token)) {
|
|
|
// 前置运算符 (开根号, sin, cos, tan) 直接入栈
|
|
|
// 后置运算符 (平方) 需要等待其操作数先计算出来
|
|
|
// 在标准调度场算法中,后置运算符通常在遇到时立即处理其栈顶的操作数
|
|
|
// 这里我们可以在遇到 "平方" 时,立即对 numberStack 的顶部元素进行平方操作
|
|
|
if ("平方".equals(token)) {
|
|
|
if (numberStack.isEmpty()) {
|
|
|
throw new IllegalArgumentException("Invalid expression: '平方' lacks an operand.");
|
|
|
}
|
|
|
double operand = numberStack.pop();
|
|
|
numberStack.push(Math.pow(operand, 2));
|
|
|
} else { // "开根号", "sin", "cos", "tan" 是前置运算符
|
|
|
operatorStack.push(token);
|
|
|
}
|
|
|
} else if (OPERATORS.contains(token)) {
|
|
|
// 处理四则运算符,遵循优先级
|
|
|
while (!operatorStack.isEmpty() &&
|
|
|
!operatorStack.peek().equals("(") &&
|
|
|
PRECEDENCE.get(token) <= PRECEDENCE.getOrDefault(operatorStack.peek(), 0)) {
|
|
|
processOperator(numberStack, operatorStack.pop());
|
|
|
}
|
|
|
operatorStack.push(token);
|
|
|
} else {
|
|
|
throw new IllegalArgumentException("Unknown token: " + token);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 处理栈中剩余的所有运算符
|
|
|
while (!operatorStack.isEmpty()) {
|
|
|
String op = operatorStack.pop();
|
|
|
if (op.equals("(") || op.equals(")")) {
|
|
|
throw new IllegalArgumentException("Mismatched parentheses in expression.");
|
|
|
}
|
|
|
processOperator(numberStack, op);
|
|
|
}
|
|
|
|
|
|
if (numberStack.size() != 1 || !operatorStack.isEmpty()) {
|
|
|
throw new IllegalArgumentException("Invalid expression: " + String.join(" ", expressionTokens));
|
|
|
}
|
|
|
|
|
|
return numberStack.pop();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 执行一次运算操作。
|
|
|
*
|
|
|
* @param numberStack 数字栈
|
|
|
* @param operator 运算符
|
|
|
*/
|
|
|
private static void processOperator(Stack<Double> numberStack, String operator) {
|
|
|
if (numberStack.size() < 1) {
|
|
|
throw new IllegalArgumentException("Invalid expression: operator '" + operator + "' lacks operand(s).");
|
|
|
}
|
|
|
|
|
|
if (ADVANCED_OPERATORS.contains(operator)) {
|
|
|
double operand = numberStack.pop();
|
|
|
switch (operator) {
|
|
|
case "开根号":
|
|
|
if (operand < 0) {
|
|
|
// 抛出异常,让调用者(MultipleChoiceGenerator)处理
|
|
|
throw new ArithmeticException("Cannot take square root of negative number: " + operand);
|
|
|
}
|
|
|
numberStack.push(Math.sqrt(operand));
|
|
|
break;
|
|
|
case "sin":
|
|
|
numberStack.push(Math.sin(Math.toRadians(operand))); // 假设输入是度数
|
|
|
break;
|
|
|
case "cos":
|
|
|
numberStack.push(Math.cos(Math.toRadians(operand)));
|
|
|
break;
|
|
|
case "tan":
|
|
|
// tan(90 + n*180) 会趋向无穷,这里不特别处理,让其返回 Infinity 或 -Infinity
|
|
|
numberStack.push(Math.tan(Math.toRadians(operand)));
|
|
|
break;
|
|
|
default:
|
|
|
throw new IllegalArgumentException("Unknown advanced operator: " + operator);
|
|
|
}
|
|
|
} else if (OPERATORS.contains(operator)) {
|
|
|
if (numberStack.size() < 2) {
|
|
|
throw new IllegalArgumentException("Invalid expression: operator '" + operator + "' lacks operand(s).");
|
|
|
}
|
|
|
double b = numberStack.pop();
|
|
|
double a = numberStack.pop();
|
|
|
double result;
|
|
|
switch (operator) {
|
|
|
case "+":
|
|
|
result = a + b;
|
|
|
break;
|
|
|
case "-":
|
|
|
result = a - b;
|
|
|
break;
|
|
|
case "*":
|
|
|
result = a * b;
|
|
|
break;
|
|
|
case "/":
|
|
|
if (b == 0) {
|
|
|
throw new ArithmeticException("Division by zero");
|
|
|
}
|
|
|
result = a / b;
|
|
|
break;
|
|
|
default:
|
|
|
throw new IllegalArgumentException("Unknown operator: " + operator);
|
|
|
}
|
|
|
numberStack.push(result);
|
|
|
} else {
|
|
|
throw new IllegalArgumentException("Unexpected operator in process: " + operator);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public static boolean isNumeric(String str) {
|
|
|
if (str == null || str.isEmpty()) {
|
|
|
return false;
|
|
|
}
|
|
|
try {
|
|
|
Double.parseDouble(str);
|
|
|
return true;
|
|
|
} catch (NumberFormatException e) {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
} |