为了账号安全,请及时绑定邮箱和手机立即绑定

如何评估以字符串形式给出的数学表达式?

如何评估以字符串形式给出的数学表达式?

偶然的你 2019-05-22 14:57:15
如何评估以字符串形式给出的数学表达式?我正在尝试编写一个Java例程来评估简单的数学表达式,String例如:"5+3""10-40""10*3"我想避免很多if-then-else语句。我怎样才能做到这一点?
查看完整描述

6 回答

?
Cats萌萌

TA贡献1805条经验 获得超9个赞

我已经eval为算术表达式编写了这个方法来回答这个问题。它执行加法,减法,乘法,除法,取幂(使用^符号)和一些基本函数sqrt。它支持使用(...进行分组),它可以使运算符优先级关联性规则正确。

public static double eval(final String str) {

    return new Object() {

        int pos = -1, ch;


        void nextChar() {

            ch = (++pos < str.length()) ? str.charAt(pos) : -1;

        }


        boolean eat(int charToEat) {

            while (ch == ' ') nextChar();

            if (ch == charToEat) {

                nextChar();

                return true;

            }

            return false;

        }


        double parse() {

            nextChar();

            double x = parseExpression();

            if (pos < str.length()) throw new RuntimeException("Unexpected: " + (char)ch);

            return x;

        }


        // Grammar:

        // expression = term | expression `+` term | expression `-` term

        // term = factor | term `*` factor | term `/` factor

        // factor = `+` factor | `-` factor | `(` expression `)`

        //        | number | functionName factor | factor `^` factor


        double parseExpression() {

            double x = parseTerm();

            for (;;) {

                if      (eat('+')) x += parseTerm(); // addition

                else if (eat('-')) x -= parseTerm(); // subtraction

                else return x;

            }

        }


        double parseTerm() {

            double x = parseFactor();

            for (;;) {

                if      (eat('*')) x *= parseFactor(); // multiplication

                else if (eat('/')) x /= parseFactor(); // division

                else return x;

            }

        }


        double parseFactor() {

            if (eat('+')) return parseFactor(); // unary plus

            if (eat('-')) return -parseFactor(); // unary minus


            double x;

            int startPos = this.pos;

            if (eat('(')) { // parentheses

                x = parseExpression();

                eat(')');

            } else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers

                while ((ch >= '0' && ch <= '9') || ch == '.') nextChar();

                x = Double.parseDouble(str.substring(startPos, this.pos));

            } else if (ch >= 'a' && ch <= 'z') { // functions

                while (ch >= 'a' && ch <= 'z') nextChar();

                String func = str.substring(startPos, this.pos);

                x = parseFactor();

                if (func.equals("sqrt")) x = Math.sqrt(x);

                else if (func.equals("sin")) x = Math.sin(Math.toRadians(x));

                else if (func.equals("cos")) x = Math.cos(Math.toRadians(x));

                else if (func.equals("tan")) x = Math.tan(Math.toRadians(x));

                else throw new RuntimeException("Unknown function: " + func);

            } else {

                throw new RuntimeException("Unexpected: " + (char)ch);

            }


            if (eat('^')) x = Math.pow(x, parseFactor()); // exponentiation


            return x;

        }

    }.parse();

}

例:


System.out.println(eval("((4 - 2^3 + 1) * -sqrt(3*3+4*4)) / 2"));

输出:7.5 (这是正确的)


解析器是递归下降解析器,因此内部对其语法中的每个级别的运算符优先级使用单独的解析方法。我保持简短,所以很容易修改,但这里有一些想法,你可能想要扩展它:

  • 变量:

    通过查找传递给eval方法的变量表中的名称(如a),可以轻松更改读取函数名称的解析器位以处理自定义变量Map<String,Double> variables

  • 单独的编译和评估:

    如果在添加对变量的支持后,您希望使用已更改的变量对相同的表达式进行数百万次计算,而不是每次都进行解析,该怎么办?这是可能的。首先定义用于评估预编译表达式的接口:

    @FunctionalInterfaceinterface Expression {
        double eval();}

    现在更改返回doubles的所有方法,因此它们返回该接口的实例。Java 8的lambda语法非常适用于此。其中一个更改方法的示例:

    Expression parseExpression() {
        Expression x = parseTerm();
        for (;;) {
            if (eat('+')) { // addition
                Expression a = x, b = parseTerm();
                x = (() -> a.eval() + b.eval());
            } else if (eat('-')) { // subtraction
                Expression a = x, b = parseTerm();
                x = (() -> a.eval() - b.eval());
            } else {
                return x;
            }
        }}

    这构建了一个Expression表示编译表达式的对象的递归树(一个抽象语法树)。然后你可以编译一次并用不同的值重复评估它:

    public static void main(String[] args) {
        Map<String,Double> variables = new HashMap<>();
        Expression exp = parse("x^2 - x + 2", variables);
        for (double x = -20; x <= +20; x++) {
            variables.put("x", x);
            System.out.println(x + " => " + exp.eval());
        }}
  • 不同的数据类型:

    而不是double,你可以改变评估者使用更强大的东西BigDecimal,或者实现复杂数字或有理数(分数)的类。您甚至可以使用Object,允许在表达式中混合使用某种数据类型,就像真正的编程语言一样。:)


查看完整回答
反对 回复 2019-05-22
?
繁华开满天机

TA贡献1816条经验 获得超4个赞

解决这个问题的正确方法是使用词法分析器解析器。您可以自己编写这些的简单版本,或者这些页面也有Java词法分析器和解析器的链接。

创建递归下降解析器是一个非常好的学习练习。


查看完整回答
反对 回复 2019-05-22
?
慕慕森

TA贡献1856条经验 获得超17个赞

如果Java应用程序已经访问数据库,则可以轻松地评估表达式,而无需使用任何其他JAR。

有些数据库要求您使用虚拟表(例如,Oracle的“双”表),而其他数据库则允许您在不从任何表“选择”的情况下评估表达式。

例如,在Sql Server或Sqlite中

select (((12.10 +12.0))/ 233.0) amount

在Oracle中

select (((12.10 +12.0))/ 233.0) amount from dual;

使用DB的优点是您可以同时评估多个表达式。此外,大多数DB都允许您使用高度复杂的表达式,并且还可以根据需要调用许多额外的函数。

但是,如果需要单独评估许多单个表达式,特别是当DB位于网络服务器上时,性能可能会受到影响。

以下通过使用Sqlite内存数据库在一定程度上解决了性能问题。

这是Java中的一个完整的工作示例

Class. forName("org.sqlite.JDBC");Connection conn = DriverManager.getConnection("jdbc:sqlite::memory:");Statement stat = conn.createStatement();
ResultSet rs = stat.executeQuery( "select (1+10)/20.0 amount");rs.next();System.out.println(rs.getBigDecimal(1));stat.close();conn.close();

当然,您可以扩展上面的代码以同时处理多个计算。

ResultSet rs = stat.executeQuery( "select (1+10)/20.0 amount, (1+100)/20.0 amount2");


查看完整回答
反对 回复 2019-05-22
  • 6 回答
  • 0 关注
  • 669 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信