用变量评估数学表达式。 (java 8) [英] Evaluating a math expression with variables. (java 8)
问题描述
对于此问题的解答,我需要其他帮助,评估以字符串形式给出的数学表达式。 @Boann用户使用非常有趣的算法回答了这个问题,他还指出可以更改为接受变量。我设法对其进行了修改,并使它能够正常工作,但不知道他是如何将编译和评估分开的。这是我的代码:
I would like additional help on a answer to this question, Evaluating a math expression given in string form. The user @Boann answered the question with a very interesting algorithm that he also points out can be altered to accept variables. I've managed to alter it and get it to work, but dont know how he separates the compilation and evaluation. Here's my code:
import java.util.HashMap;
import java.util.Map;
public class EvaluateExpressionWithVariabels {
@FunctionalInterface
interface Expression {
double eval();
}
public static void main(String[] args){
Map<String,Double> variables = new HashMap<>();
for (double x = 100; x <= +120; x++) {
variables.put("x", x);
System.out.println(x + " => " + eval("x+(sqrt(x))",variables).eval());
}
}
public static Expression eval(final String str,Map<String,Double> variables) {
return new Object() {
int pos = -1, ch;
//if check pos+1 is smaller than string length ch is char at new pos
void nextChar() {
ch = (++pos < str.length()) ? str.charAt(pos) : -1;
}
//skips 'spaces' and if current char is what was searched, if true move to next char return true
//else return false
boolean eat(int charToEat) {
while (ch == ' ') nextChar();
if (ch == charToEat) {
nextChar();
return true;
}
return false;
}
Expression parse() {
nextChar();
Expression 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
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 parseTerm() {
Expression x = parseFactor();
for (;;) {
if (eat('*')){
Expression a = x, b = parseFactor(); // multiplication
x = (() -> a.eval() * b.eval());
}
else if(eat('/')){
Expression a = x, b = parseFactor(); // division
x = (() -> a.eval() / b.eval());
}
else return x;
}
}
Expression parseFactor() {
if (eat('+')) return parseFactor(); // unary plus
if (eat('-')){
Expression b = parseFactor(); // unary minus
return (() -> -1 * b.eval());
}
Expression 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();
}
double xx = Double.parseDouble(str.substring(startPos, this.pos));
x = () -> xx;
} else if (ch >= 'a' && ch <= 'z') { // functions
while (ch >= 'a' && ch <= 'z') nextChar();
String func = str.substring(startPos, this.pos);
if ( variables.containsKey(func)){
x = () -> variables.get(func);
}else{
double xx = parseFactor().eval();
if (func.equals("sqrt")) x = () -> Math.sqrt(xx);
else if (func.equals("sin")) x = () -> Math.sin(Math.toRadians(xx));
else if (func.equals("cos")) x = () -> Math.cos(Math.toRadians(xx));
else if (func.equals("tan")) x = () -> Math.tan(Math.toRadians(xx));
else throw new RuntimeException("Unknown function: " + func);
}
} else {
throw new RuntimeException("Unexpected: " + (char)ch);
}
if (eat('^')){
x = () -> {
double d = parseFactor().eval();
return Math.pow(d,d); // exponentiation
};
}
return x;
}
}.parse();
}
}
如果看看他的回答,他的主要意思
If you take a look at his answer his main
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());
}
}
他调用函数 parse
在此行上 Expression exp = parse( x ^ 2--x + 2,变量);
一次编译表达式并使用for使用唯一的x值对其进行多次评估。 parse
函数指的是什么。
He calls function parse
on this line Expression exp = parse("x^2 - x + 2", variables);
to compile the expression once and uses the for to evaluate it multiple times with a unique x values. What does the parse
function refer to.
ps:我对用户问题发表了评论,没有任何答复。 / p>
ps: I have commented on the users question with no reply.
推荐答案
抱歉,造成混乱。我提到的 parse
函数只是现有的 eval
函数,但是由于它返回了表达式
对象。
Sorry for the confusion. The "parse
" function I referred to is simply the existing eval
function, but renamed since it returns an Expression
object.
因此,您将拥有:
public static Expression parse(String str, Map<String,Double> variables) { ... }
并通过以下方式调用它:
And invoke it by:
Map<String,Double> variables = new HashMap<>();
Expression exp = parse("x+(sqrt(x))", variables);
for (double x = 100; x <= +120; x++) {
variables.put("x", x);
System.out.println(x + " => " + exp.eval());
}
另一件事:有必要在解析时知道名称是否指代变量或函数,以便知道它是否接受参数,但是在解析期间您不能在变量映射上调用 containsKey
在调用 exp.eval()
之前不会出现在地图中!一种解决方案是改为在地图中放置函数,因此您可以在其上调用 containsKey
:
One other thing: It's necessary to know at parse time whether a name refers to a variable or a function, in order to know whether or not it takes an argument, but you can't call containsKey
on the variables map during the parse, since the variables might not be present in the map until exp.eval()
is called! One solution is to put functions in a map instead, so you can call containsKey
on that:
} else if (ch >= 'a' && ch <= 'z') { // functions and variables
while (ch >= 'a' && ch <= 'z') nextChar();
String name = str.substring(startPos, this.pos);
if (functions.containsKey(name)) {
DoubleUnaryOperator func = functions.get(name);
Expression arg = parseFactor();
x = () -> func.applyAsDouble(arg.eval());
} else {
x = () -> variables.get(name);
}
} else {
然后在类级别的某个地方初始化功能
映射:
And then somewhere at class level, initialize the functions
map:
private static final Map<String,DoubleUnaryOperator> functions = new HashMap<>();
static {
functions.put("sqrt", x -> Math.sqrt(x));
functions.put("sin", x -> Math.sin(Math.toRadians(x)));
functions.put("cos", x -> Math.cos(Math.toRadians(x)));
functions.put("tan", x -> Math.tan(Math.toRadians(x)));
}
(也可以定义函数
映射为解析器内部的局部变量,但这会在每次解析过程中增加一些开销。)
(It would also be okay to define the functions
map as a local variable inside the parser, but that adds a little bit more overhead during each parse.)
这篇关于用变量评估数学表达式。 (java 8)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!