猿问

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

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

我正在尝试编写一个Java例程来评估简单的数学表达式,String例如:

  1. "5+3"

  2. "10-40"

  3. "10*3"

我想避免很多if-then-else语句。我怎样才能做到这一点?


偶然的你
浏览 670回答 6
6回答

Cats萌萌

我已经eval为算术表达式编写了这个方法来回答这个问题。它执行加法,减法,乘法,除法,取幂(使用^符号)和一些基本函数sqrt。它支持使用(...进行分组),它可以使运算符优先级和关联性规则正确。public static double eval(final String str) {&nbsp; &nbsp; return new Object() {&nbsp; &nbsp; &nbsp; &nbsp; int pos = -1, ch;&nbsp; &nbsp; &nbsp; &nbsp; void nextChar() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ch = (++pos < str.length()) ? str.charAt(pos) : -1;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; boolean eat(int charToEat) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; while (ch == ' ') nextChar();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (ch == charToEat) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; nextChar();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return true;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return false;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; double parse() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; nextChar();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; double x = parseExpression();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (pos < str.length()) throw new RuntimeException("Unexpected: " + (char)ch);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return x;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; // Grammar:&nbsp; &nbsp; &nbsp; &nbsp; // expression = term | expression `+` term | expression `-` term&nbsp; &nbsp; &nbsp; &nbsp; // term = factor | term `*` factor | term `/` factor&nbsp; &nbsp; &nbsp; &nbsp; // factor = `+` factor | `-` factor | `(` expression `)`&nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp; &nbsp; &nbsp; | number | functionName factor | factor `^` factor&nbsp; &nbsp; &nbsp; &nbsp; double parseExpression() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; double x = parseTerm();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for (;;) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if&nbsp; &nbsp; &nbsp; (eat('+')) x += parseTerm(); // addition&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; else if (eat('-')) x -= parseTerm(); // subtraction&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; else return x;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; double parseTerm() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; double x = parseFactor();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for (;;) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if&nbsp; &nbsp; &nbsp; (eat('*')) x *= parseFactor(); // multiplication&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; else if (eat('/')) x /= parseFactor(); // division&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; else return x;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; double parseFactor() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (eat('+')) return parseFactor(); // unary plus&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (eat('-')) return -parseFactor(); // unary minus&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; double x;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; int startPos = this.pos;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (eat('(')) { // parentheses&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; x = parseExpression();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; eat(')');&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; while ((ch >= '0' && ch <= '9') || ch == '.') nextChar();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; x = Double.parseDouble(str.substring(startPos, this.pos));&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else if (ch >= 'a' && ch <= 'z') { // functions&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; while (ch >= 'a' && ch <= 'z') nextChar();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; String func = str.substring(startPos, this.pos);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; x = parseFactor();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (func.equals("sqrt")) x = Math.sqrt(x);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; else if (func.equals("sin")) x = Math.sin(Math.toRadians(x));&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; else if (func.equals("cos")) x = Math.cos(Math.toRadians(x));&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; else if (func.equals("tan")) x = Math.tan(Math.toRadians(x));&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; else throw new RuntimeException("Unknown function: " + func);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; throw new RuntimeException("Unexpected: " + (char)ch);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (eat('^')) x = Math.pow(x, parseFactor()); // exponentiation&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return x;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }.parse();}例:System.out.println(eval("((4 - 2^3 + 1) * -sqrt(3*3+4*4)) / 2"));输出:7.5&nbsp;(这是正确的)解析器是递归下降解析器,因此内部对其语法中的每个级别的运算符优先级使用单独的解析方法。我保持简短,所以很容易修改,但这里有一些想法,你可能想要扩展它:变量:通过查找传递给eval方法的变量表中的名称(如a),可以轻松更改读取函数名称的解析器位以处理自定义变量Map<String,Double> variables。单独的编译和评估:如果在添加对变量的支持后,您希望使用已更改的变量对相同的表达式进行数百万次计算,而不是每次都进行解析,该怎么办?这是可能的。首先定义用于评估预编译表达式的接口:@FunctionalInterfaceinterface&nbsp;Expression&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;double&nbsp;eval();}现在更改返回doubles的所有方法,因此它们返回该接口的实例。Java 8的lambda语法非常适用于此。其中一个更改方法的示例:Expression&nbsp;parseExpression()&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;Expression&nbsp;x&nbsp;=&nbsp;parseTerm(); &nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;(;;)&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(eat('+'))&nbsp;{&nbsp;//&nbsp;addition &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Expression&nbsp;a&nbsp;=&nbsp;x,&nbsp;b&nbsp;=&nbsp;parseTerm(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x&nbsp;=&nbsp;(()&nbsp;->&nbsp;a.eval()&nbsp;+&nbsp;b.eval()); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;else&nbsp;if&nbsp;(eat('-'))&nbsp;{&nbsp;//&nbsp;subtraction &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Expression&nbsp;a&nbsp;=&nbsp;x,&nbsp;b&nbsp;=&nbsp;parseTerm(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x&nbsp;=&nbsp;(()&nbsp;->&nbsp;a.eval()&nbsp;-&nbsp;b.eval()); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;else&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;x; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;&nbsp;&nbsp;}}这构建了一个Expression表示编译表达式的对象的递归树(一个抽象语法树)。然后你可以编译一次并用不同的值重复评估它:public&nbsp;static&nbsp;void&nbsp;main(String[]&nbsp;args)&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;Map<String,Double>&nbsp;variables&nbsp;=&nbsp;new&nbsp;HashMap<>(); &nbsp;&nbsp;&nbsp;&nbsp;Expression&nbsp;exp&nbsp;=&nbsp;parse("x^2&nbsp;-&nbsp;x&nbsp;+&nbsp;2",&nbsp;variables); &nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;(double&nbsp;x&nbsp;=&nbsp;-20;&nbsp;x&nbsp;<=&nbsp;+20;&nbsp;x++)&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;variables.put("x",&nbsp;x); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(x&nbsp;+&nbsp;"&nbsp;=>&nbsp;"&nbsp;+&nbsp;exp.eval()); &nbsp;&nbsp;&nbsp;&nbsp;}}不同的数据类型:而不是double,你可以改变评估者使用更强大的东西BigDecimal,或者实现复杂数字或有理数(分数)的类。您甚至可以使用Object,允许在表达式中混合使用某种数据类型,就像真正的编程语言一样。:)

繁华开满天机

解决这个问题的正确方法是使用词法分析器和解析器。您可以自己编写这些的简单版本,或者这些页面也有Java词法分析器和解析器的链接。创建递归下降解析器是一个非常好的学习练习。

慕慕森

如果Java应用程序已经访问数据库,则可以轻松地评估表达式,而无需使用任何其他JAR。有些数据库要求您使用虚拟表(例如,Oracle的“双”表),而其他数据库则允许您在不从任何表“选择”的情况下评估表达式。例如,在Sql Server或Sqlite中select&nbsp;(((12.10&nbsp;+12.0))/&nbsp;233.0)&nbsp;amount在Oracle中select&nbsp;(((12.10&nbsp;+12.0))/&nbsp;233.0)&nbsp;amount&nbsp;from&nbsp;dual;使用DB的优点是您可以同时评估多个表达式。此外,大多数DB都允许您使用高度复杂的表达式,并且还可以根据需要调用许多额外的函数。但是,如果需要单独评估许多单个表达式,特别是当DB位于网络服务器上时,性能可能会受到影响。以下通过使用Sqlite内存数据库在一定程度上解决了性能问题。这是Java中的一个完整的工作示例Class.&nbsp;forName("org.sqlite.JDBC");Connection&nbsp;conn&nbsp;=&nbsp;DriverManager.getConnection("jdbc:sqlite::memory:");Statement&nbsp;stat&nbsp;=&nbsp;conn.createStatement(); ResultSet&nbsp;rs&nbsp;=&nbsp;stat.executeQuery(&nbsp;"select&nbsp;(1+10)/20.0&nbsp;amount");rs.next();System.out.println(rs.getBigDecimal(1));stat.close();conn.close();当然,您可以扩展上面的代码以同时处理多个计算。ResultSet&nbsp;rs&nbsp;=&nbsp;stat.executeQuery(&nbsp;"select&nbsp;(1+10)/20.0&nbsp;amount,&nbsp;(1+100)/20.0&nbsp;amount2");
随时随地看视频慕课网APP

相关分类

Java
我要回答