当前位置: 首页 > article >正文

java实现表达式计算

功能特点:

  1. 灵活的函数注册

    • 固定参数函数:registerFunction("sqrt", 1, ...)

    • 可变参数函数:registerFunction("max", -1, ...)

  2. 丰富的内置函数

    • 数学函数:sin、cos、sqrt

    • 统计函数:max(可自动扩展)

  3. 严谨的错误处理

    • 未定义变量检测

    • 除零错误检测

    • 参数个数校验

    • 括号匹配检查

  4. 高性能设计

    • 采用逆波兰表达式算法

    • 单次解析多次求值

    • 线程安全设计

该实现通过扩展Tokenizer识别函数调用,改进Parser处理参数列表,并增加函数注册机制,使工具能够灵活支持各种数学函数的扩展需求。

import java.util.*;
import java.util.function.Function;

public class ExpressionCalculator {

    public static void main(String[] args) {
        // 使用示例
        Expression expr = new Expression("sin(π/2) + max(3, 5, 2)");
        Map<String, Double> variables = new HashMap<>();
        variables.put("π", Math.PI);
        
        try {
            double result = expr.evaluate(variables);
            System.out.println("计算结果: " + result);  // 输出: 1.0 + 5 = 6.0
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        
        Expression expr2 = new Expression("sin(x) + log(y, 10)");
        Map<String, Double> variables2 = new HashMap<>();
        variables2.put("x", Math.PI/2);
        variables2.put("y", 100.0);
        
        // 注册log函数(以10为底)
       
        
        double result = expr2.evaluate(variables2); // sin(π/2)=1, log10(100)=2 → 结果=3
        System.out.println(result);
    }

    static class Expression {
        private final List<Token> postfixTokens;
        private static final Map<String, RegisteredFunction> functions = new HashMap<>();

        static {
            // 注册内置函数
            registerFunction("sin", 1, args -> Math.sin(args[0]));
            registerFunction("cos", 1, args -> Math.cos(args[0]));
            registerFunction("sqrt", 1, args -> {
                if (args[0] < 0) throw new ArithmeticException("负数开平方");
                return Math.sqrt(args[0]);
            });
            registerFunction("max", -1, args -> 
            Arrays.stream(args)
                  .mapToDouble(Double::doubleValue)
                  .max()
                  .orElseThrow(() -> new ArithmeticException("至少需要一个参数"))
        );
        
        // 可选的min函数修正
        registerFunction("min", -1, args -> 
            Arrays.stream(args)
                  .mapToDouble(Double::doubleValue)
                  .min()
                  .orElseThrow(() -> new ArithmeticException("至少需要一个参数"))
        );
        registerFunction("log", 2, args -> Math.log10(args[0])/Math.log10(args[1]));
        }

        public static void registerFunction(String name, int argCount, Function<Double[], Double> function) {
            functions.put(name, new RegisteredFunction(argCount, function));
        }

        public Expression(String expression) {
            Tokenizer tokenizer = new Tokenizer(expression);
            List<Token> infixTokens = tokenizer.tokenize();
            this.postfixTokens = Parser.infixToPostfix(infixTokens);
        }

        public double evaluate(Map<String, Double> variables) {
            Deque<Double> stack = new ArrayDeque<>();
            
            for (Token token : postfixTokens) {
                switch (token.type) {
                    case NUMBER:
                        stack.push(token.number);
                        break;
                    case VARIABLE:
                        handleVariable(token, stack, variables);
                        break;
                    case FUNCTION:
                        handleFunction(token, stack);
                        break;
                    case OPERATOR:
                        handleOperator(token.operator, stack);
                        break;
                    default:
                        throw new IllegalStateException("意外的token类型");
                }
            }
            
            if (stack.size() != 1) {
                throw new ArithmeticException("无效的表达式");
            }
            return stack.pop();
        }

        private void handleVariable(Token token, Deque<Double> stack, Map<String, Double> vars) {
            String varName = token.variable;
            if (!vars.containsKey(varName)) {
                throw new IllegalArgumentException("未定义变量: " + varName);
            }
            stack.push(vars.get(varName));
        }

        private void handleFunction(Token token, Deque<Double> stack) {
            String funcName = token.variable;
            int argCount = token.argumentCount;
            RegisteredFunction func = functions.get(funcName);
            
            if (func == null) throw new IllegalArgumentException("未注册函数: " + funcName);
            if (func.argCount != -1 && argCount != func.argCount) {
                throw new IllegalArgumentException("参数个数错误: " + funcName);
            }
            
            Double[] args = new Double[argCount];
            for (int i = argCount-1; i >= 0; i--) {
                args[i] = stack.pop();
            }
            stack.push(func.function.apply(args));
        }

        private void handleOperator(char op, Deque<Double> stack) {
            if (op == 'u') {
                stack.push(-stack.pop());
            } else {
                double b = stack.pop(), a = stack.pop();
                switch (op) {
                    case '+': stack.push(a + b); break;
                    case '-': stack.push(a - b); break;
                    case '*': stack.push(a * b); break;
                    case '/': 
                        if (b == 0) throw new ArithmeticException("除零错误");
                        stack.push(a / b); 
                        break;
                    case '^': stack.push(Math.pow(a, b)); break;
                    default: throw new IllegalArgumentException("无效运算符");
                }
            }
        }

        private static class RegisteredFunction {
            int argCount; // -1表示可变参数
            Function<Double[], Double> function;

            RegisteredFunction(int argCount, Function<Double[], Double> function) {
                this.argCount = argCount;
                this.function = function;
            }
        }
    }

    // 以下是Tokenizer、Parser和Token类的实现(改进版)
    
    enum TokenType { NUMBER, VARIABLE, FUNCTION, OPERATOR, LEFT_PAREN, RIGHT_PAREN, COMMA }

    static class Token {
        final TokenType type;
        Double number;
        String variable;
        Character operator;
        int argumentCount;

        private Token(TokenType type) { this.type = type; }

        static Token number(double value) {
            Token t = new Token(TokenType.NUMBER);
            t.number = value;
            return t;
        }

        static Token variable(String name) {
            Token t = new Token(TokenType.VARIABLE);
            t.variable = name;
            return t;
        }

        static Token function(String name, int argCount) {
            Token t = new Token(TokenType.FUNCTION);
            t.variable = name;
            t.argumentCount = argCount;
            return t;
        }

        static Token operator(char op) {
            Token t = new Token(TokenType.OPERATOR);
            t.operator = op;
            return t;
        }

        static Token leftParen() { return new Token(TokenType.LEFT_PAREN); }
        static Token rightParen() { return new Token(TokenType.RIGHT_PAREN); }
        static Token comma() { return new Token(TokenType.COMMA); }
    }

    static class Tokenizer {
        private final String input;
        private int pos = 0;
        private char currentChar;

        Tokenizer(String input) {
            this.input = input.replaceAll("\\s+", "");
            this.currentChar = !this.input.isEmpty() ? this.input.charAt(0) : '\0';
        }

        List<Token> tokenize() {
            List<Token> tokens = new ArrayList<>();
            while (currentChar != '\0') {
                if (Character.isDigit(currentChar) || currentChar == '.') {
                    tokens.add(readNumber());
                } else if (Character.isLetter(currentChar)) {
                    tokens.add(readIdentifier());
                } else if ("+-*/^".indexOf(currentChar) != -1) {
                    tokens.add(readOperator());
                } else if (currentChar == '(') {
                    tokens.add(Token.leftParen());
                    advance();
                } else if (currentChar == ')') {
                    tokens.add(Token.rightParen());
                    advance();
                } else if (currentChar == ',') {
                    tokens.add(Token.comma());
                    advance();
                } else {
                    throw new RuntimeException("无效字符: " + currentChar);
                }
            }
            return tokens;
        }

        private Token readNumber() {
            StringBuilder sb = new StringBuilder();
            boolean hasDecimal = false;
            while (currentChar != '\0' && (Character.isDigit(currentChar) || currentChar == '.')) {
                if (currentChar == '.') {
                    if (hasDecimal) throw new RuntimeException("无效数字格式");
                    hasDecimal = true;
                }
                sb.append(currentChar);
                advance();
            }
            try {
                return Token.number(Double.parseDouble(sb.toString()));
            } catch (NumberFormatException e) {
                throw new RuntimeException("无效数字: " + sb);
            }
        }

        private Token readIdentifier() {
            StringBuilder sb = new StringBuilder();
            while (currentChar != '\0' && Character.isLetterOrDigit(currentChar)) {
                sb.append(currentChar);
                advance();
            }
            if (currentChar == '(') {
                return Token.function(sb.toString(), 0);
            } else {
                return Token.variable(sb.toString());
            }
        }

        private Token readOperator() {
            char op = currentChar;
            advance();
            return Token.operator(op);
        }

        private void advance() {
            pos++;
            currentChar = (pos < input.length()) ? input.charAt(pos) : '\0';
        }
    }

    static class Parser {
        static List<Token> infixToPostfix(List<Token> infixTokens) {
            List<Token> output = new ArrayList<>();
            Deque<Token> stack = new ArrayDeque<>();
            Deque<Integer> argCountStack = new ArrayDeque<>();

            for (Token token : infixTokens) {
                switch (token.type) {
                    case NUMBER: case VARIABLE:
                        output.add(token); break;
                    case FUNCTION:
                        stack.push(token);
                        argCountStack.push(1);
                        break;
                    case LEFT_PAREN:
                        stack.push(token); break;
                    case COMMA:
                        handleComma(output, stack, argCountStack); break;
                    case RIGHT_PAREN:
                        handleRightParen(output, stack, argCountStack); break;
                    case OPERATOR:
                        handleOperator(token, output, stack); break;
                }
            }

            while (!stack.isEmpty()) {
                Token t = stack.pop();
                if (t.type == TokenType.LEFT_PAREN) throw new RuntimeException("括号不匹配");
                output.add(t);
            }
            return output;
        }

        private static void handleComma(List<Token> output, Deque<Token> stack, Deque<Integer> argCountStack) {
            while (!stack.isEmpty() && stack.peek().type != TokenType.LEFT_PAREN) {
                output.add(stack.pop());
            }
            if (!argCountStack.isEmpty()) {
                argCountStack.push(argCountStack.pop() + 1);
            }
        }

        private static void handleRightParen(List<Token> output, Deque<Token> stack, Deque<Integer> argCountStack) {
            while (!stack.isEmpty() && stack.peek().type != TokenType.LEFT_PAREN) {
                output.add(stack.pop());
            }
            if (stack.isEmpty()) throw new RuntimeException("括号不匹配");
            stack.pop(); // 移除左括号

            if (!stack.isEmpty() && stack.peek().type == TokenType.FUNCTION) {
                Token func = stack.pop();
                int args = argCountStack.pop();
                output.add(Token.function(func.variable, args));
            }
        }

        private static void handleOperator(Token token, List<Token> output, Deque<Token> stack) {
            while (!stack.isEmpty() && precedence(stack.peek().operator) >= precedence(token.operator)) {
                output.add(stack.pop());
            }
            stack.push(token);
        }

        private static int precedence(Character op) {
            if (op == null) return -1;
            switch (op) {
                case '^': return 4;
                case '*': case '/': return 3;
                case '+': case '-': return 2;
                default: return -1;
            }
        }
    }
}


http://www.kler.cn/a/548165.html

相关文章:

  • Zookeeper(47)如何在Zookeeper中设置节点数据?
  • AI 编程工具—Cursor 进阶篇 数据分析
  • rustdesk远程桌面自建服务器
  • RagFlow部署
  • Java8适配的markdown转换html工具(FlexMark)
  • 【C/C++】C++ Vector容器核心操作指南:增删改查全面解析
  • Qt QCommandLinkButton 总结
  • 图的遍历: 广度优先遍历和深度优先遍历
  • no matching cipher found问题一次解决经历
  • 【数据分享】1929-2024年全球站点的逐日降雪深度数据(Shp\Excel\免费获取)
  • python 查询mongo数据批量插入mysql
  • 【devops】Github Actions Secrets | 如何在Github中设置CI的Secret供CI的yaml使用
  • Redis6.2.6下载和安装
  • 硕成C语言22【一些算法和数组的概念】
  • LVS的NAT及DR模式
  • Cookie的学习2.15
  • RadASM环境,win32汇编入门教程之四
  • CAS单点登录(第7版)24.高可用性
  • C语言中的文件
  • C#学习之S参数读取(s2p文件)