我正在尝试编写一个Java例程来评估像这样的String
值的数学表达式:
"5+3"
"10-4*5"
"(1+10)*3"
我想避免很多if-else语句。 我该怎么做?
我正在尝试编写一个Java例程来评估像这样的String
值的数学表达式:
"5+3"
"10-4*5"
"(1+10)*3"
我想避免很多if-else语句。 我该怎么做?
在JDK1.6中,您可以使用内置的JavaScript引擎。
import javax.script.ScriptEngineManager;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
public class Test {
public static void main(String[] args) throws ScriptException {
ScriptEngineManager mgr = new ScriptEngineManager();
ScriptEngine engine = mgr.getEngineByName("JavaScript");
String foo = "40+2";
System.out.println(engine.eval(foo));
}
}
return (Double) engine.eval(foo);
- Ben Visnessnew javax.script.ScriptEngineManager().getEngineByName("JavaScript") .eval("var f = new java.io.FileWriter('hello.txt'); f.write('UNLIMITED POWER!'); f.close();");
-- 这段代码将通过 JavaScript 写入一个文件到程序的当前目录(默认情况下)。 - Boanneval
方法,以回答这个问题。它支持加法、减法、乘法、除法、指数运算(使用^
符号)和一些基本函数,如sqrt
。它支持使用(
...)
进行分组,并正确处理运算符precedence和associativity规则。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 `(` expression `)` | 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();
if (!eat(')')) throw new RuntimeException("Missing ')'");
} 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);
if (eat('(')) {
x = parseExpression();
if (!eat(')')) throw new RuntimeException("Missing ')' after argument to " + func);
} else {
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"));
解析器是一个递归下降解析器, 因此在其语法中为每个运算符优先级级别使用单独的解析方法。我故意保持它简短,但是这里有一些你可能想要扩展的想法:
Variables:
The bit of the parser that reads the names for functions can easily be changed to handle custom variables too, by looking up names in a variable table passed to the eval
method, such as a Map<String,Double> variables
.
Separate compilation and evaluation:
What if, having added support for variables, you wanted to evaluate the same expression millions of times with changed variables, without parsing it every time? It's possible. First define an interface to use to evaluate the precompiled expression:
@FunctionalInterface
interface Expression {
double eval();
}
Now to rework the original "eval" function into a "parse" function, change all the methods that return double
s, so instead they return an instance of that interface. Java 8's lambda syntax works well for this. Example of one of the changed methods:
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;
}
}
}
That builds a recursive tree of Expression
objects representing the compiled expression (an abstract syntax tree). Then you can compile it once and evaluate it repeatedly with different values:
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());
}
}
Different datatypes:
Instead of double
, you could change the evaluator to use something more powerful like BigDecimal
, or a class that implements complex numbers, or rational numbers (fractions). You could even use Object
, allowing some mix of datatypes in expressions, just like a real programming language. :)
为了我的大学项目,我正在寻找一个支持基本公式和更复杂方程(特别是迭代运算符)的解析器/求值器。我找到了一个非常好的开源库,名为mXparser,可用于JAVA和.NET。我将提供一些示例以让您对语法有所感觉,如需进一步说明,请访问项目网站(特别是教程部分)。
https://mathparser.org/mxparser-tutorial/
以下是一些示例:
1 - 简单公式
Expression e = new Expression("( 2 + 3/4 + sin(pi) )/2");
double v = e.calculate()
2 - 用户自定义参数和常量
Argument x = new Argument("x = 10");
Constant a = new Constant("a = pi^2");
Expression e = new Expression("cos(a*x)", x, a);
double v = e.calculate()
3 - 用户自定义函数
Function f = new Function("f(x, y, z) = sin(x) + cos(y*z)");
Expression e = new Expression("f(3,2,5)", f);
double v = e.calculate()
4 - 迭代
Expression e = new Expression("sum( i, 1, 100, sin(i) )");
double v = e.calculate()
最近有发现 - 如果您想尝试语法(并查看高级用例),您可以下载由 mXparser 提供支持的 Scalar 计算器 应用程序。
这里是另一个名为EvalEx的GitHub开源库。
与JavaScript引擎不同,该库专注于仅评估数学表达式。此外,该库可扩展,并支持使用布尔运算符以及括号。
Interpreter interpreter = new Interpreter();
interpreter.eval("result = (7+21*6)/(32-27)");
System.out.println(interpreter.get("result"));
如果您的Java应用程序已经访问数据库,则可以轻松评估表达式,而不使用任何其他JAR包。
有些数据库要求您使用虚拟表(例如Oracle的“dual”表),而其他数据库允许您在不从任何表中“选择”的情况下评估表达式。
例如,在Sql Server或Sqlite中
select (((12.10 +12.0))/ 233.0) amount
并且在Oracle中
select (((12.10 +12.0))/ 233.0) amount from dual;
使用数据库的优点在于,您可以同时评估许多表达式。此外,大多数数据库将允许您使用高度复杂的表达式,并且还会有许多额外的函数可以根据需要调用。
但是,如果需要单独评估许多单个表达式,特别是当数据库位于网络服务器上时,性能可能会受到影响。
以下部分地解决了性能问题,通过使用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");
ExpressionParser parser = new SpelExpressionParser();
int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2
double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); //24.0
这篇文章讨论了不同的方法。以下是文章提到的两种关键方法:
允许脚本包括对Java对象的引用。
// Create or retrieve a JexlEngine
JexlEngine jexl = new JexlEngine();
// Create an expression object
String jexlExp = "foo.innerFoo.bar()";
Expression e = jexl.createExpression( jexlExp );
// Create a context and add data
JexlContext jctx = new MapContext();
jctx.set("foo", new Foo() );
// Now evaluate the expression, getting the result
Object o = e.evaluate(jctx);
private static void jsEvalWithVariable()
{
List<String> namesList = new ArrayList<String>();
namesList.add("Jill");
namesList.add("Bob");
namesList.add("Laureen");
namesList.add("Ed");
ScriptEngineManager mgr = new ScriptEngineManager();
ScriptEngine jsEngine = mgr.getEngineByName("JavaScript");
jsEngine.put("namesListKey", namesList);
System.out.println("Executing in script environment...");
try
{
jsEngine.eval("var x;" +
"var names = namesListKey.toArray();" +
"for(x in names) {" +
" println(names[x]);" +
"}" +
"namesListKey.add(\"Dana\");");
}
catch (ScriptException ex)
{
ex.printStackTrace();
}
}
While there are still tokens to be read in,
1.1 Get the next token. 1.2 If the token is:
1.2.1 A number: push it onto the value stack.
1.2.2 A variable: get its value, and push onto the value stack.
1.2.3 A left parenthesis: push it onto the operator stack.
1.2.4 A right parenthesis:
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 An operator (call it 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.
While the operator stack is not empty, 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.
At this point the operator stack should be empty, and the value stack should have only one value in it, which is the final result.