I am writing a class to evaluate an arithmetic expression, For now my class can convert an infix expression into postfix, it doesn't support exponents yet.
public class Evaluator {
public Map<String, Operator> mOperators = new HashMap<>();
public Evaluator() {
addOperator(new Operator("+", 1) {
@Override
public BigDecimal eval(BigDecimal a, BigDecimal b) {
return a.add(b);
}
});
addOperator(new Operator("-", 1) {
@Override
public BigDecimal eval(BigDecimal a, BigDecimal b) {
return a.subtract(b);
}
});
addOperator(new Operator("*", 2) {
@Override
public BigDecimal eval(BigDecimal a, BigDecimal b) {
return a.multiply(b);
}
});
addOperator(new Operator("/", 2) {
@Override
public BigDecimal eval(BigDecimal a, BigDecimal b) {
return a.divide(b);
}
});
}
public String toPostfix(String infix) {
Stack<String> operators = new Stack<>();
StringBuilder result = new StringBuilder();
String[] tokens = infix.replaceAll("\\s+", "").split("(?<=[^.a-zA-Z\\d])|(?=[^.a-zA-Z\\d])");
for (String token : tokens) {
if (token.matches("-?\\d+(\\.\\d+)?")) {
result.append(token).append(" ");
} else if (operators.isEmpty() || operators.peek().equals("(") || token.equals("(")) {
operators.push(token);
} else if (token.equals(")")) {
while (!operators.peek().equals("(")) {
result.append(operators.pop()).append(" ");
}
operators.pop();
} else {
while (!operators.isEmpty() && precedence(operators.peek()) >= precedence(token)) {
result.append(operators.pop()).append(" ");
}
operators.push(token);
}
}
while (!operators.isEmpty()) {
result.append(operators.pop()).append(" ");
}
return result.toString().trim();
}
public void addOperator(Operator operator) {
mOperators.put(operator.getSymbol(), operator);
}
private int precedence(String token) {
Operator operator = mOperators.getOrDefault(token, null);
return operator == null ? -1 : operator.getPrecedence();
}
public abstract class Operator {
private final String mSymbol;
private final int mPrecedence;
public Operator(String symbol, int precedence) {
mSymbol = symbol;
mPrecedence = precedence;
}
public abstract BigDecimal eval(BigDecimal a, BigDecimal b);
public String getSymbol() {
return mSymbol;
}
public int getPrecedence() {
return mPrecedence;
}
}
}
My questions are:
- How to deal with negative number, e.g:
(-1) + 2
- Is there any easier or better way to evaluate an arithmetic expression without having to turn it into postfix?
- What can I do to optimize my class and make it better?
-
1\$\begingroup\$ Welcome to Code Review. Your question is fine, but I would remove the negative number question. We're here to review the current code, we're not coding new functionality. \$\endgroup\$Marc-Andre– Marc-Andre2017年02月23日 15:19:55 +00:00Commented Feb 23, 2017 at 15:19
2 Answers 2
The two most common approaches to evaluating simple mathematical expressions are :
- Parse the expression into an Abstract Syntax Tree, and then evaluate the tree.
- Process the expression using Dijkstra's Shunting-yard algorithm.
Is there any reason to not use the great built-in expression evaluator: Nashorn Javascript Engine?
It can calculate for you all sorts of expressions supported by ES6. Example of usage (in Java 9):
@Test
public void calculate() throws ScriptException {
String MATH_EXPR = "-1 + x * 2";
Bindings bindings = new SimpleBindings( new HashMap<>( Map.of("x", 2) ) );
Double result = (Double) jse.eval(MATH_EXPR, bindings);
out.printf("x = %d, %s = %.1f%n", bindings.get("x"), MATH_EXPR, result);
assertEquals( Double.valueOf(3.0), result );
}