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 PRECEDENCE = new HashMap<>(); private static final Map 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 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 output = new java.util.ArrayList<>(); Stack 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 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(); } }