1 While the thing on top of the operator stack is not a
left parenthesis,
1 Pop the operator from the operator stack.
2 Pop the value stack twice, getting two operands.
3 Apply the operator to the operands, in the correct order.
4 Push the result onto the value stack.
2 Pop the left parenthesis from the operator stack, and discard it.
1.2.5运营商(称之为thisOp):
1 While the operator stack is not empty, and the top thing on the
operator stack has the same or greater precedence as thisOp,
1 Pop the operator from the operator stack.
2 Pop the value stack twice, getting two operands.
3 Apply the operator to the operands, in the correct order.
4 Push the result onto the value stack.
2 Push thisOp onto the operator stack.
ExprEvaluator util = new ExprEvaluator();
IExpr result = util.evaluate("10-40");
System.out.println(result.toString()); // -> "-30"
请注意,可以评估明确更复杂的表达式:
// D(...) gives the derivative of the function Sin(x)*Cos(x)
IAST function = D(Times(Sin(x), Cos(x)), x);
IExpr result = util.evaluate(function);
// print: Cos(x)^2-Sin(x)^2
24 回答
使用JDK1.6,您可以使用内置的Javascript引擎 .
我已经为算术表达式编写了这个
eval
方法来回答这个问题 . 它执行加法,减法,乘法,除法,取幂(使用^
符号)和一些基本函数,如sqrt
. 它支持使用(
...)
进行分组,并且它使运算符precedence和associativity规则正确 .例:
输出:7.5 (which is correct)
解析器是recursive descent parser,因此内部对其语法中的每个级别的运算符优先级使用单独的解析方法 . 我保留了它 short 所以它很容易修改,但是这里有一些你想要扩展它的想法:
通过查找传递给
eval
方法的变量表中的名称(例如Map<String,Double> variables
),可以轻松更改读取函数名称的解析器位以处理自定义变量 .如果在添加对变量的支持后,您希望使用已更改的变量对相同的表达式进行数百万次计算,而不是每次都进行解析,该怎么办?这是可能的 . 首先定义用于评估预编译表达式的接口:
现在更改返回
double
的所有方法,因此它们返回该接口的实例 . Java 8的lambda语法非常适用于此 . 其中一个更改方法的示例:这构建了一个递归的
Expression
对象树,表示编译后的表达式(abstract syntax tree) . 然后你可以编译一次并用不同的值重复评估它:而不是
double
,您可以更改评估程序以使用更强大的功能,如BigDecimal
,或实现复数或有理数(分数)的类 . 您甚至可以使用Object
,在表达式中允许混合使用某种数据类型,就像真正的编程语言一样 . :)此答案中的所有代码都发布到公共领域 . 玩得开心!
解决这个问题的正确方法是lexer和parser . 您可以自己编写这些的简单版本,或者这些页面也有Java词法分析器和解析器的链接 .
创建递归下降解析器是一个非常好的学习练习 .
HERE是GitHub上另一个名为EvalEx的开源库 .
与JavaScript引擎不同,此库专注于仅评估数学表达式 . 此外,该库是可扩展的,并支持使用布尔运算符和括号 .
您也可以尝试BeanShell解释器:
如果Java应用程序已经访问数据库,则可以轻松地计算表达式,而无需使用任何其他JAR .
有些数据库要求您使用虚拟表(例如,Oracle的“双”表),而其他数据库则允许您在不从任何表“选择”的情况下评估表达式 .
例如,在Sql Server或Sqlite中
在Oracle中
使用DB的优点是您可以同时评估多个表达式 . 此外,大多数数据库允许您使用高度复杂的表达式,并且还可以根据需要调用许多额外的函数 .
但是,如果需要单独评估许多单个表达式,特别是当DB位于网络服务器上时,性能可能会受到影响 .
以下通过使用Sqlite内存数据库在一定程度上解决了性能问题 .
这是Java中的完整工作示例
当然,您可以扩展上面的代码以同时处理多个计算 .
对于我的大学项目,我一直在寻找一个解析器/评估器,支持基本公式和更复杂的方程(特别是迭代运算符) . 我找到了非常好的JAVA和.NET开源库,名为mXparser . 我将举几个例子来对语法有所了解,如需进一步说明,请访问项目网站(特别是教程部分) .
http://mathparser.org/
http://mathparser.org/mxparser-tutorial/
http://mathparser.org/api/
几个例子
1 - 简单的furmula
2 - 用户定义的参数和常量
3 - 用户定义的功能
4 - 迭代
最好的祝福
This article指向3种不同的方法,一种是JEXL from Apache,并允许包含对java对象的引用的脚本 .
另一种方法是使用Spring Expression Language或SpEL,它可以在评估数学表达式的同时做更多的工作,因此可能有点过分 . 您不必使用Spring框架来使用此表达式库,因为它是独立的 . 从SpEL复制示例文档:
阅读更简洁的SpEL示例here和完整的文档here
这是另一个有趣的选择https://github.com/Shy-Ta/expression-evaluator-demo
用法非常简单,可以完成工作,例如:
我认为无论你做什么,它都会涉及很多条件语句 . 但对于像您的示例中的单个操作,如果语句类似,则可以将其限制为4
当你想要处理像“4 5 * 6”这样的多个操作时,它会变得更加复杂 .
如果你正在尝试构建一个计算器,那么我将分别通过计算的每个部分(每个数字或运算符)而不是单个字符串 .
似乎JEP应该做的工作
如果我们要实现它,那么我们可以使用以下算法: -
1.1获取下一个标记 . 1.2如果令牌是:
1.2.1数字:将其推送到值堆栈 .
1.2.2变量:获取其值,并推入值栈 .
1.2.3左括号:将其推入操作员堆栈 .
1.2.4右括号:
1.2.5运营商(称之为thisOp):
当操作员堆栈不为空时,1从操作员堆栈中弹出操作员 . 2弹出值栈两次,得到两个操作数 . 3以正确的顺序将操作符应用于操作数 . 4将结果推送到值堆栈 .
此时操作符堆栈应为空,并且值堆栈中只应包含一个值,这是最终结果 .
您可以查看Symja framework:
请注意,可以评估明确更复杂的表达式:
还有一个选择:https://github.com/stefanhaustein/expressionparser
我实现了这个有一个简单但灵活的选项,允许两者:
立即处理(Calculator.java,SetDemo.java)
构建和处理解析树(TreeBuilder.java)
上面链接的TreeBuilder是符号派生的CAS demo package的一部分 . 还有一个BASIC interpreter示例,我已经开始使用它构建TypeScript interpreter .
这实际上补充了@Boann给出的答案 . 它有一个小错误导致"-2 ^ 2"给出-4.0的错误结果 . 这个问题就是在他的评估中取幂的点 . 只需将取幂移动到parseTerm()块,你就会好起来的 . 看看下面的内容,这是@Boann's answer略有修改 . 修改在评论中 .
使用JDK1.6的Javascript引擎和代码注入处理来尝试以下示例代码 .
}
这样的事情怎么样:
并相应地为每个其他数学运算符做类似的事情 .
可以使用Djikstra's shunting-yard algorithm将中缀表示法中的任何表达式字符串转换为后缀表示法 . 然后,算法的结果可以作为postfix algorithm的输入,并返回表达式的结果 .
我写了一篇关于它的文章here, with an implementation in java
回答为时已晚,但我遇到了同样的情况来评估java中的表达式,它可能对某人有所帮助
MVEL
对表达式进行运行时评估,我们可以在String
中编写一个java代码来对其进行求值 .像RHINO或NASHORN这样的外部库可以用来运行javascript . 并且javascript可以评估简单的公式而无需对字符串进行分区 . 如果代码写得好,也没有性能影响 . 下面是RHINO的一个例子 -
一个可以评估数学表达式的Java类: