上篇说了怎么实现一个不带括号的表达式求值方法,由于现在的带了括号了,之前的方法就不适用了。
上篇对应地址:java 栈的应用 - 实现简单的加减乘除的计算器
这里主要是分两步:
- 将给定的输入转成后缀表达式;
- 根据后缀表达式进行运算;
先看转后缀表达式的代码:
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 中缀表达式转后缀表达式
*/
public class ToPostfixExpression {
private static final int CAPACITY = Integer.MAX_VALUE;
private final Stack<Operator> ops = new Stack<>();
private final Stack<String> results = new Stack<>();
private final Pattern numPattern = Pattern.compile("\\d*\\.?\\d*");
private final Pattern opPattern = Pattern.compile("[+\\-*/()]");
public String apply(String input) {
// 1 + ((2+3) * 4 ) - 5
input = input.replaceAll(" ", "");
Matcher numMatcher = numPattern.matcher(input);
Matcher opm = opPattern.matcher(input);
for (int pos = 0; pos < input.length(); ) {
boolean found = numMatcher.find(pos);
if (found) {
int start = numMatcher.start();
int end = numMatcher.end();
pos = end;
if (start != end) {
String sub = input.substring(start, end);
// System.out.printf("number: (%d, %d)=%s\n", start, end, sub);
results.push(sub);
}
}
found = opm.find(pos);
if (found) {
int start = opm.start();
int end = opm.end();
pos = end;
if (start != end) {
try {
String sub = input.substring(start, end);
// System.out.printf("op: (%d, %d)=%s\n", start, end, sub);
Operator action = Operator.convert(sub);
if (action == Operator.LEFT) {
ops.push(action);
} else if (action == Operator.RIGHT) {
// System.err.println("before pop------ " + ops);
Operator ele;
do {
ele = ops.pop();
if (ele != Operator.LEFT) {
results.push(ele.getOperate());
}
} while (ele != Operator.LEFT);
// System.err.println("pop------ " + ops);
} else {
boolean actionPushed = false;
while (!actionPushed) {
if (ops.isEmpty()) {
ops.push(action);
actionPushed = true;
} else {
Operator top = ops.pop();
if (Operator.LEFT == top
|| action.getPriority() > top.getPriority()) {
ops.push(top);
ops.push(action);
actionPushed = true;
} else {
results.push(top.getOperate());
}
}
// System.out.println("in loop ------ " + ops);
}
}
} catch (Operator.IllegalOperationException e) {
throw new RuntimeException(e);
}
}
}
}
// after loop, pop all ele from ops to results;
while (!ops.isEmpty()) {
Operator op = ops.pop();
results.push(op.getOperate());
}
System.out.println("after loop, results: " + results);
// revert the results, then the final stack is the result.
StringBuilder sb = new StringBuilder();
while (!results.isEmpty()) {
String ele = results.pop();
sb.insert(0, ele.concat(", "));
}
int last = sb.lastIndexOf(", ");
if (last != -1) {
sb.delete(last, last + 2);
}
return sb.toString().trim();
}
public static void main(String[] args) {
// String input = "123 + ((21+3.7) * 4 ) - 5";
String input = "(3 +4.23.4 ) * 5 -6...";
ToPostfixExpression expression = new ToPostfixExpression();
String apply = expression.apply(input);
System.out.printf("transform(%s) -> [%s]\n", input, apply);
}
}
输出如下:
transform((3 +4.23.4 ) * 5 -6) -> [3, 4.23, +, 5, *, 6, -]
然后是计算后缀表达式:
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Stack;
public class Calculator {
private static final String NUM_REGEX = "\\d*\\.?\\d*";
private static final String OP_REGEX = "[+\\-*/]";
private final Stack<BigDecimal> numbers = new Stack<>();
private final ToPostfixExpression transformer = new ToPostfixExpression();
public String apply(String input) {
String postfixExpression = transformer.apply(input).replaceAll(" ", "");
System.out.println("postfixExpression: " + postfixExpression);
return calcPostfixExpression(postfixExpression);
}
private String calcPostfixExpression(String postfixExpression) {
String[] elements = postfixExpression.split(",");
System.out.println("split:::: " + Arrays.toString(elements));
for (String ele : elements) {
if (ele.matches(NUM_REGEX)) {
double value = Double.parseDouble(ele);
BigDecimal bv = BigDecimal.valueOf(value);
numbers.push(bv);
} else if (ele.matches(OP_REGEX)) {
try {
Operator op = Operator.convert(ele);
BigDecimal top = numbers.pop();
BigDecimal prev = numbers.pop();
BigDecimal result = Operator.calculate(op, prev, top);
System.out.printf("%s %s %s = %s\n", prev, op, top, result);
numbers.push(result);
} catch (Operator.IllegalOperationException e) {
throw new RuntimeException(e);
}
}
}
return numbers.pop().stripTrailingZeros().toPlainString();
}
public static void main(String[] args) {
Calculator cal = new Calculator();
// String input = "(3 +4 ) * 5 -6";
String input = "123 + ((21+3.7) * 4 ) - 5"; // error
// String input = "1 + ((2+3) * 4 ) - 5";
String result = cal.apply(input);
System.out.printf("compute(%s) -> [%s]\n", input, result);
}
}
输出如下:
compute(123 + ((21+3.7) * 4 ) - 5) -> [216.8]
里面用到的 Operator
是自定义的操作符封装类,代码如下:
import java.math.BigDecimal;
import java.math.RoundingMode;
public enum Operator {
LEFT("(", 0),
RIGHT(")", 0),
ADD("+", 1),
REDUCE("-", 1),
MULTI("*", 2),
DIVIDE("/", 2);
private final String operate;
private final int priority;
Operator(String operate, int priority) {
this.operate = operate;
this.priority = priority;
}
public String getOperate() {
return operate;
}
public int getPriority() {
return priority;
}
public static Operator convert(String operate)
throws IllegalOperationException {
return switch (operate) {
case "+" -> ADD;
case "-" -> REDUCE;
case "*" -> MULTI;
case "/" -> DIVIDE;
case "(" -> LEFT;
case ")" -> RIGHT;
default -> throw new IllegalOperationException("illegal operator: " + operate);
};
}
public static int calculate(Operator op, int prev, int cur)
throws IllegalOperationException {
return switch (op) {
case ADD -> prev + cur;
case REDUCE -> prev - cur;
case MULTI -> prev * cur;
case DIVIDE -> prev / cur;
default -> throw new IllegalOperationException("illegal operator.");
};
}
public static BigDecimal calculate(Operator op, BigDecimal prev, BigDecimal cur)
throws IllegalOperationException {
return switch (op) {
case ADD -> prev.add(cur);
case REDUCE -> prev.subtract(cur);
case MULTI -> prev.multiply(cur);
case DIVIDE -> prev.divide(cur, RoundingMode.HALF_UP);
default -> throw new IllegalOperationException("illegal operator.");
};
}
public static class IllegalOperationException extends Exception {
public IllegalOperationException(String message) {
super(message);
}
}
}
以上就实现了一个可以计算 带括号的加减乘除的算术表达式(比如"123 + ((21+3.7) * 4 ) - 5"
)的值了。
大体上使用了栈去进行中间值的存储,使用了正则去分离表达式中的数字与操作符。(这里限定操作符只能是+-*/()
这几个)
然后,并没有去验证输入的合法性。因为这个太复杂了感觉…
注意,这里的转后缀及计算后缀的算法是波兰数学家扬·武卡谢维奇提出的。
可以看对应的百科解释:逆波兰式