As I was looking through my past questions, I noticed my really old calculator question (here and here). Considering it looked like a huge mess, I decided to rewrite it. Coincidentally, the April 2015 Community Challenge was to implement a simple calculator. It might be a bit late, but here it is:
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
public class Calculator {
private static final Pattern NUMBER_PATTERN = Pattern
.compile("\\d+(\\.\\d+)?");
public static double calculate(String equation) {
return calculate(equation, Operation.getLastInPrecedence());
}
private static double calculate(String equation, Operation currentOp) {
Operation nextOp = currentOp.getPreviousInPrecedence();
if (NUMBER_PATTERN.matcher(equation).matches()) {
return Double.parseDouble(equation);
}
String[] equationParts = equation.split("\\s*"
+ Pattern.quote(Character.toString(currentOp.getSymbol()))
+ "\\s*");
double result;
if (nextOp == null) {
// Last operation
result = Math.pow(Integer
.parseInt(equationParts[equationParts.length - 2]), Integer
.parseInt(equationParts[equationParts.length - 1]));
for (int i = equationParts.length - 3; i >= 0; i--) {
result = Math.pow(Integer.parseInt(equationParts[i]), result);
}
} else {
result = calculate(equationParts[0], nextOp);
for (int i = 1; i < equationParts.length; i++) {
result = currentOp.execute(result, calculate(equationParts[1],
nextOp));
}
}
return result;
}
}
class Operation {
public static final Operation ADD = new Operation('+', 1);
public static final Operation SUBTRACT = new Operation('-', 2);
public static final Operation MULTIPLY = new Operation('*', 3);
public static final Operation DIVIDE = new Operation('/', 4);
public static final Operation POW = new Operation('^', 5);
private static final Map<Integer, Operation> operations = new HashMap<>();
static {
operations.put(ADD.precedence, ADD);
operations.put(SUBTRACT.precedence, SUBTRACT);
operations.put(MULTIPLY.precedence, MULTIPLY);
operations.put(DIVIDE.precedence, DIVIDE);
operations.put(POW.precedence, POW);
}
private final char symbol;
private final int precedence;
private Operation(char symbol, int precedence) {
this.symbol = symbol;
this.precedence = precedence;
}
public char getSymbol() {
return symbol;
}
public int getPrecedence() {
return precedence;
}
public double execute(double left, double right) {
if (this == ADD) {
return left + right;
} else if (this == SUBTRACT) {
return left - right;
} else if (this == MULTIPLY) {
return left * right;
} else if (this == DIVIDE) {
return left / right;
} else if (this == POW) {
return Math.pow(left, right);
} else {
// Not Possible
throw new InternalError();
}
}
public Operation getNextInPrecedence() {
return operations.get(precedence - 1);
}
public Operation getPreviousInPrecedence() {
return operations.get(precedence + 1);
}
public static Operation getFromPrecedence(int precedence) {
return operations.get(precedence);
}
public static Operation getLastInPrecedence() {
return ADD;
}
public static Operation getFirstInPrecedence() {
return POW;
}
}
The logic here is fairly simple:
- I have a recursive solution that goes through the operations, in reverse precedence. This way, the highest precedence operations are calculated first.
- The method will first check if the given equation is a number. If it is, it just returns the parsed number.
- If not, it continues executing. It will first split the equation into the parts that are separated by the current operation, and calculates them by recursively calling this method.
- If it is the last operation (i.e.
pow
or^
), then it will perform a different method, going from right to left. Explanation will appear later in this post. Explanation:
In addition (or subtraction or multiplication or whatever), you go from left to right:
1 + 2 + 3
is essentially the same as:
$$1 + 2 + 3$$
but with pow
, you have to go from right to left.
2 ^ 3 ^ 2
Is the same as:
$$2 ^ {3 ^ 2}$$
As long as you go from right to left. If you go from left to right though, it will result in:
$$(2 ^ 3) ^ 2$$
\$2 ^ {3 ^ 2}\$ is 512
, which does not equal \$(2 ^ 3) ^ 2\$ (64
).
- Once all the operations are complete, it will either return the result, or throw an exception due to a over-complicated equation.
I also have tests:
class Test {
@Test
public void testAdd() {
assertEquals(6.0, Calculator.calculate("3.5 + 2.5"));
}
@Test
public void testSubtract() {
assertEquals(1.0, Calculator.calculate("3.5 - 2.5"));
}
@Test
public void testMultiply() {
assertEquals(8.75, Calculator.calculate("3.5 * 2.5"));
}
@Test
public void testDivide() {
assertEquals(1.4, Calculator.calculate("3.5 / 2.5"));
}
@Test
public void testPow() {
assertEquals(9.0, Calculator.calculate("3 ^ 2"));
}
}
Major concerns:
- The code seems to be fixed for this specific problem. If I were to add another operation, the design has to be changed a lot. Is there a way to design it in a more flexible way?
- Are my tests fine?
- Does it do the job in the most efficient way?