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.
math-learing/src/main/java/com/personalproject/service/MathExpressionEvaluator.java

270 lines
8.1 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 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();
}
}