|
|
package com.personalproject.service;
|
|
|
|
|
|
import java.util.HashMap;
|
|
|
import java.util.Map;
|
|
|
import java.util.Stack;
|
|
|
import java.util.function.DoubleUnaryOperator;
|
|
|
import java.util.regex.Pattern;
|
|
|
|
|
|
/**
|
|
|
* 可处理基本算术运算的数学表达式求值器.
|
|
|
*/
|
|
|
public final class MathExpressionEvaluator {
|
|
|
|
|
|
private static final Pattern NUMBER_PATTERN = Pattern.compile("-?\\d+(\\.\\d+)?");
|
|
|
private static final Map<Character, Integer> PRECEDENCE = new HashMap<>();
|
|
|
private static final Map<String, DoubleUnaryOperator> FUNCTIONS = new HashMap<>();
|
|
|
|
|
|
static {
|
|
|
PRECEDENCE.put('+', 1);
|
|
|
PRECEDENCE.put('-', 1);
|
|
|
PRECEDENCE.put('*', 2);
|
|
|
PRECEDENCE.put('/', 2);
|
|
|
PRECEDENCE.put('^', 3);
|
|
|
|
|
|
FUNCTIONS.put("sin", angle -> Math.sin(Math.toRadians(angle)));
|
|
|
FUNCTIONS.put("cos", angle -> Math.cos(Math.toRadians(angle)));
|
|
|
FUNCTIONS.put("tan", angle -> Math.tan(Math.toRadians(angle)));
|
|
|
FUNCTIONS.put("sqrt", Math::sqrt);
|
|
|
}
|
|
|
|
|
|
private MathExpressionEvaluator() {
|
|
|
// 防止实例化此工具类
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 计算数学表达式字符串的结果.
|
|
|
*
|
|
|
* @param expression 要计算的数学表达式
|
|
|
* @return 计算结果
|
|
|
* @throws IllegalArgumentException 如果表达式无效
|
|
|
*/
|
|
|
public static double evaluate(String expression) {
|
|
|
if (expression == null) {
|
|
|
throw new IllegalArgumentException("Expression cannot be null");
|
|
|
}
|
|
|
|
|
|
expression = expression.replaceAll("\\s+", ""); // 移除空白字符
|
|
|
if (expression.isEmpty()) {
|
|
|
throw new IllegalArgumentException("Expression cannot be empty");
|
|
|
}
|
|
|
|
|
|
// 将表达式拆分为记号
|
|
|
String[] tokens = tokenize(expression);
|
|
|
|
|
|
// 使用调度场算法将中缀表达式转换为后缀表达式
|
|
|
String[] postfix = infixToPostfix(tokens);
|
|
|
|
|
|
// 计算后缀表达式
|
|
|
return evaluatePostfix(postfix);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 将表达式拆分为数字与运算符的记号.
|
|
|
*
|
|
|
* @param expression 待拆分的表达式
|
|
|
* @return 记号数组
|
|
|
*/
|
|
|
private static String[] tokenize(String expression) {
|
|
|
java.util.List<String> tokens = new java.util.ArrayList<>();
|
|
|
StringBuilder currentNumber = new StringBuilder();
|
|
|
|
|
|
for (int i = 0; i < expression.length(); i++) {
|
|
|
char c = expression.charAt(i);
|
|
|
|
|
|
if (Character.isDigit(c) || c == '.') {
|
|
|
currentNumber.append(c);
|
|
|
} else if (Character.isLetter(c)) {
|
|
|
if (currentNumber.length() > 0) {
|
|
|
tokens.add(currentNumber.toString());
|
|
|
currentNumber.setLength(0);
|
|
|
}
|
|
|
StringBuilder functionBuilder = new StringBuilder();
|
|
|
functionBuilder.append(c);
|
|
|
while (i + 1 < expression.length() && Character.isLetter(expression.charAt(i + 1))) {
|
|
|
functionBuilder.append(expression.charAt(++i));
|
|
|
}
|
|
|
String function = functionBuilder.toString();
|
|
|
if (!isFunction(function)) {
|
|
|
throw new IllegalArgumentException("Unsupported function: " + function);
|
|
|
}
|
|
|
tokens.add(function);
|
|
|
} else if (c == '(' || c == ')') {
|
|
|
if (currentNumber.length() > 0) {
|
|
|
tokens.add(currentNumber.toString());
|
|
|
currentNumber.setLength(0);
|
|
|
}
|
|
|
tokens.add(String.valueOf(c));
|
|
|
} else if (isOperator(c)) {
|
|
|
if (currentNumber.length() > 0) {
|
|
|
tokens.add(currentNumber.toString());
|
|
|
currentNumber.setLength(0);
|
|
|
}
|
|
|
|
|
|
// 处理一元负号
|
|
|
if (c == '-' && (i == 0 || expression.charAt(i - 1) == '(')) {
|
|
|
currentNumber.append(c);
|
|
|
} else {
|
|
|
tokens.add(String.valueOf(c));
|
|
|
}
|
|
|
} else {
|
|
|
throw new IllegalArgumentException("Invalid character in expression: " + c);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (currentNumber.length() > 0) {
|
|
|
tokens.add(currentNumber.toString());
|
|
|
}
|
|
|
|
|
|
return tokens.toArray(new String[0]);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 检查字符是否为运算符.
|
|
|
*
|
|
|
* @param c 待检查的字符
|
|
|
* @return 若字符是运算符则返回 true,否则返回 false
|
|
|
*/
|
|
|
private static boolean isOperator(char c) {
|
|
|
return c == '+' || c == '-' || c == '*' || c == '/' || c == '^';
|
|
|
}
|
|
|
|
|
|
private static boolean isFunction(String token) {
|
|
|
return FUNCTIONS.containsKey(token);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 使用调度场算法将中缀表达式转换为后缀表达式.
|
|
|
*
|
|
|
* @param tokens 中缀表达式的记号数组
|
|
|
* @return 后缀表达式的记号数组
|
|
|
*/
|
|
|
private static String[] infixToPostfix(String[] tokens) {
|
|
|
java.util.List<String> output = new java.util.ArrayList<>();
|
|
|
Stack<String> operators = new Stack<>();
|
|
|
|
|
|
for (String token : tokens) {
|
|
|
if (isNumber(token)) {
|
|
|
output.add(token);
|
|
|
} else if (isFunction(token)) {
|
|
|
operators.push(token);
|
|
|
} else if (token.equals("(")) {
|
|
|
operators.push(token);
|
|
|
} else if (token.equals(")")) {
|
|
|
while (!operators.isEmpty() && !operators.peek().equals("(")) {
|
|
|
output.add(operators.pop());
|
|
|
}
|
|
|
if (!operators.isEmpty()) {
|
|
|
operators.pop(); // 移除 "("
|
|
|
}
|
|
|
if (!operators.isEmpty() && isFunction(operators.peek())) {
|
|
|
output.add(operators.pop());
|
|
|
}
|
|
|
} else if (isOperator(token.charAt(0))) {
|
|
|
while (!operators.isEmpty()
|
|
|
&& isOperator(operators.peek().charAt(0))
|
|
|
&& PRECEDENCE.get(operators.peek().charAt(0)) >= PRECEDENCE.get(token.charAt(0))) {
|
|
|
output.add(operators.pop());
|
|
|
}
|
|
|
operators.push(token);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
while (!operators.isEmpty()) {
|
|
|
String operator = operators.pop();
|
|
|
if (operator.equals("(") || operator.equals(")")) {
|
|
|
throw new IllegalArgumentException("Mismatched parentheses in expression");
|
|
|
}
|
|
|
output.add(operator);
|
|
|
}
|
|
|
|
|
|
return output.toArray(new String[0]);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 计算后缀表达式的值.
|
|
|
*
|
|
|
* @param postfix 后缀表达式的记号数组
|
|
|
* @return 计算结果
|
|
|
*/
|
|
|
private static double evaluatePostfix(String[] postfix) {
|
|
|
Stack<Double> values = new Stack<>();
|
|
|
|
|
|
for (String token : postfix) {
|
|
|
if (isNumber(token)) {
|
|
|
values.push(Double.parseDouble(token));
|
|
|
} else if (isOperator(token.charAt(0))) {
|
|
|
if (values.size() < 2) {
|
|
|
throw new IllegalArgumentException("Invalid expression: insufficient operands");
|
|
|
}
|
|
|
|
|
|
double b = values.pop();
|
|
|
double a = values.pop();
|
|
|
double result = performOperation(a, b, token.charAt(0));
|
|
|
values.push(result);
|
|
|
} else if (isFunction(token)) {
|
|
|
if (values.isEmpty()) {
|
|
|
throw new IllegalArgumentException(
|
|
|
"Invalid expression: insufficient operands for function");
|
|
|
}
|
|
|
double value = values.pop();
|
|
|
values.push(applyFunction(token, value));
|
|
|
} else {
|
|
|
throw new IllegalArgumentException("Unknown token: " + token);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (values.size() != 1) {
|
|
|
throw new IllegalArgumentException("Invalid expression: too many operands");
|
|
|
}
|
|
|
|
|
|
return values.pop();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 对两个操作数执行指定运算.
|
|
|
*
|
|
|
* @param a 第一个操作数
|
|
|
* @param b 第二个操作数
|
|
|
* @param operator 要应用的运算符
|
|
|
* @return 运算结果
|
|
|
*/
|
|
|
private static double performOperation(double a, double b, char operator) {
|
|
|
switch (operator) {
|
|
|
case '+':
|
|
|
return a + b;
|
|
|
case '-':
|
|
|
return a - b;
|
|
|
case '*':
|
|
|
return a * b;
|
|
|
case '/':
|
|
|
if (b == 0) {
|
|
|
throw new ArithmeticException("Division by zero");
|
|
|
}
|
|
|
return a / b;
|
|
|
case '^':
|
|
|
return Math.pow(a, b);
|
|
|
default:
|
|
|
throw new IllegalArgumentException("Unknown operator: " + operator);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private static double applyFunction(String function, double value) {
|
|
|
DoubleUnaryOperator operator = FUNCTIONS.get(function);
|
|
|
if (operator == null) {
|
|
|
throw new IllegalArgumentException("Unknown function: " + function);
|
|
|
}
|
|
|
return operator.applyAsDouble(value);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 检查记号是否为数字.
|
|
|
*
|
|
|
* @param token 待检查的记号
|
|
|
* @return 若为数字则返回 true,否则返回 false
|
|
|
*/
|
|
|
private static boolean isNumber(String token) {
|
|
|
return NUMBER_PATTERN.matcher(token).matches();
|
|
|
}
|
|
|
}
|