Cats萌萌
我已经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,允许在表达式中混合使用某种数据类型,就像真正的编程语言一样。:)