|
|
|
|
@ -1,21 +1,41 @@
|
|
|
|
|
package com.ybw.mathapp.service;
|
|
|
|
|
|
|
|
|
|
import java.util.*;
|
|
|
|
|
import java.util.Arrays;
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
import java.util.HashSet;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
import java.util.Set;
|
|
|
|
|
import java.util.Stack;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 扩展的计算类,支持基础四则运算、括号、平方、开根号、三角函数 (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。
|
|
|
|
|
*
|
|
|
|
|
* <p>运算符优先级定义如下:
|
|
|
|
|
* <ol>
|
|
|
|
|
* <li>括号 {@code ( )} 优先级最高。</li>
|
|
|
|
|
* <li>高级运算符 (平方, 开根号, sin, cos, tan) 优先级高于四则运算。</li>
|
|
|
|
|
* <li>平方是后置运算符,开根号和三角函数是前置运算符。</li>
|
|
|
|
|
* </ol>
|
|
|
|
|
*
|
|
|
|
|
* <p>例如:
|
|
|
|
|
* <ul>
|
|
|
|
|
* <li>{@code "3 + 开根号 16 平方"} 解释为 {@code "3 + (sqrt(16))^2"} = {@code "3 + 4^2"} = {@code "3 + 16"} = 19</li>
|
|
|
|
|
* <li>{@code "开根号 ( 4 + 5 ) 平方"} 解释为 {@code "(sqrt(4+5))^2"} = {@code "sqrt(9)^2"} = {@code "3^2"} = 9</li>
|
|
|
|
|
* <li>{@code "sin 30 + 5"} 解释为 {@code "sin(30) + 5"} (假设30是度数)</li>
|
|
|
|
|
* </ul>
|
|
|
|
|
*
|
|
|
|
|
* <p>注意: 在计算开根号时,若操作数为负数,则抛出 ArithmeticException。
|
|
|
|
|
*
|
|
|
|
|
* @author 杨博文
|
|
|
|
|
* @since 2025
|
|
|
|
|
*/
|
|
|
|
|
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 Set<String> BASIC_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 {
|
|
|
|
|
@ -34,51 +54,28 @@ public class AdvancedCaculate {
|
|
|
|
|
/**
|
|
|
|
|
* 计算给定表达式的值。
|
|
|
|
|
*
|
|
|
|
|
* @param expressionTokens 表达式分词列表,例如 ["3", "+", "开根号", "16", "平方"]
|
|
|
|
|
* <p>表达式分词列表例如: ["3", "+", "开根号", "16", "平方"]
|
|
|
|
|
*
|
|
|
|
|
* @param expressionTokens 表达式的分词列表
|
|
|
|
|
* @return 计算结果
|
|
|
|
|
* @throws IllegalArgumentException 如果表达式无效
|
|
|
|
|
* @throws ArithmeticException 如果计算过程中出现错误(如除零、负数开根号)
|
|
|
|
|
* @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);
|
|
|
|
|
|
|
|
|
|
for (String token : expressionTokens) {
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
handleClosingParenthesis(numberStack, operatorStack);
|
|
|
|
|
} 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);
|
|
|
|
|
handleAdvancedOperator(token, numberStack, operatorStack);
|
|
|
|
|
} else if (BASIC_OPERATORS.contains(token)) {
|
|
|
|
|
handleBasicOperator(token, numberStack, operatorStack);
|
|
|
|
|
} else {
|
|
|
|
|
throw new IllegalArgumentException("Unknown token: " + token);
|
|
|
|
|
}
|
|
|
|
|
@ -94,78 +91,149 @@ public class AdvancedCaculate {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (numberStack.size() != 1 || !operatorStack.isEmpty()) {
|
|
|
|
|
throw new IllegalArgumentException("Invalid expression: " + String.join(" ", expressionTokens));
|
|
|
|
|
throw new IllegalArgumentException(
|
|
|
|
|
"Invalid expression: " + String.join(" ", expressionTokens));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return numberStack.pop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理遇到右括号 ')' 的情况
|
|
|
|
|
*/
|
|
|
|
|
private static void handleClosingParenthesis(Stack<Double> numberStack,
|
|
|
|
|
Stack<String> operatorStack) {
|
|
|
|
|
while (!operatorStack.isEmpty() && !operatorStack.peek().equals("(")) {
|
|
|
|
|
processOperator(numberStack, operatorStack.pop());
|
|
|
|
|
}
|
|
|
|
|
if (!operatorStack.isEmpty()) { // Pop the "("
|
|
|
|
|
operatorStack.pop();
|
|
|
|
|
} else {
|
|
|
|
|
throw new IllegalArgumentException("Mismatched parentheses in expression.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理高级运算符(开根号, sin, cos, tan, 平方)
|
|
|
|
|
*/
|
|
|
|
|
private static void handleAdvancedOperator(String token, Stack<Double> numberStack,
|
|
|
|
|
Stack<String> operatorStack) {
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理基础四则运算符(+, -, *, /)
|
|
|
|
|
*/
|
|
|
|
|
private static void handleBasicOperator(String token, Stack<Double> numberStack,
|
|
|
|
|
Stack<String> operatorStack) {
|
|
|
|
|
// 处理四则运算符,遵循优先级
|
|
|
|
|
while (!operatorStack.isEmpty() &&
|
|
|
|
|
!operatorStack.peek().equals("(") &&
|
|
|
|
|
PRECEDENCE.get(token) <= PRECEDENCE.getOrDefault(operatorStack.peek(), 0)) {
|
|
|
|
|
processOperator(numberStack, operatorStack.pop());
|
|
|
|
|
}
|
|
|
|
|
operatorStack.push(token);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 执行一次运算操作。
|
|
|
|
|
*
|
|
|
|
|
* @param numberStack 数字栈
|
|
|
|
|
* @param operator 运算符
|
|
|
|
|
* @throws IllegalArgumentException 如果运算符缺少操作数或为未知运算符
|
|
|
|
|
* @throws ArithmeticException 如果计算过程中出现错误(如除零、负数开根号)
|
|
|
|
|
*/
|
|
|
|
|
private static void processOperator(Stack<Double> numberStack, String operator) {
|
|
|
|
|
if (numberStack.size() < 1) {
|
|
|
|
|
throw new IllegalArgumentException("Invalid expression: operator '" + operator + "' lacks operand(s).");
|
|
|
|
|
if (numberStack.isEmpty()) {
|
|
|
|
|
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);
|
|
|
|
|
processAdvancedOperator(numberStack, operator);
|
|
|
|
|
} else if (BASIC_OPERATORS.contains(operator)) {
|
|
|
|
|
processBasicOperator(numberStack, operator);
|
|
|
|
|
} else {
|
|
|
|
|
throw new IllegalArgumentException("Unexpected operator in process: " + operator);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 执行高级运算操作(开根号, sin, cos, tan)
|
|
|
|
|
*/
|
|
|
|
|
private static void processAdvancedOperator(Stack<Double> numberStack, String operator) {
|
|
|
|
|
double operand = numberStack.pop();
|
|
|
|
|
double result;
|
|
|
|
|
switch (operator) {
|
|
|
|
|
case "开根号":
|
|
|
|
|
if (operand < 0) {
|
|
|
|
|
// 抛出异常,让调用者(MultipleChoiceGenerator)处理
|
|
|
|
|
throw new ArithmeticException("Cannot take square root of negative number: " + operand);
|
|
|
|
|
}
|
|
|
|
|
result = Math.sqrt(operand);
|
|
|
|
|
break;
|
|
|
|
|
case "sin":
|
|
|
|
|
result = Math.sin(Math.toRadians(operand)); // 假设输入是度数
|
|
|
|
|
break;
|
|
|
|
|
case "cos":
|
|
|
|
|
result = Math.cos(Math.toRadians(operand));
|
|
|
|
|
break;
|
|
|
|
|
case "tan":
|
|
|
|
|
// tan(90 + n*180) 会趋向无穷,这里不特别处理,让其返回 Infinity 或 -Infinity
|
|
|
|
|
result = Math.tan(Math.toRadians(operand));
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
throw new IllegalArgumentException("Unknown advanced operator: " + operator);
|
|
|
|
|
}
|
|
|
|
|
numberStack.push(result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 执行基础四则运算操作(+, -, *, /)
|
|
|
|
|
*/
|
|
|
|
|
private static void processBasicOperator(Stack<Double> numberStack, String 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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 判断给定字符串是否为数字。
|
|
|
|
|
*
|
|
|
|
|
* @param str 待判断的字符串
|
|
|
|
|
* @return 如果是数字则返回 true,否则返回 false
|
|
|
|
|
*/
|
|
|
|
|
public static boolean isNumeric(String str) {
|
|
|
|
|
if (str == null || str.isEmpty()) {
|
|
|
|
|
return false;
|
|
|
|
|
|