实现一个简单的四则运算,听起来容易,实际上挺麻烦的。比如是实现符号的优先级,括号是最优先的,其次是乘除,然后再是加减,你要如何实现呢?再比如是给你一串字符,比如是12--2
,你如何辨别字符是负数还是减号。计算机科学的先驱 Dijkstra 给出了个简单的解决方法。
算法描述
首先输入的表达式是这样的
( ( 1 * 2 ) + ( 4 / 2 ) )
表达式由括号、数字、运算符组成的。因为数字、运算符都由空格隔开的,所以不用管太多数字解析的问题,直接将字符串以空格为分割符,分割成数组就行了。而优先级的问题全部都交给括号去完成的。
具体算法是这样的: 先定义两个栈,一个栈是装操作符(数字),一个栈装运算符(+-*/)
- 遇到
(
就忽略 - 遇到
+
这类的符号就把它压到运算符的栈(opStack) - 遇到数字就将之压到数字栈(numStack)
- 遇到
)
就从数字栈中弹出 2 个数字,从运算符栈中弹出 1 个运算符,然后根据运算符算出 2 个数字运算的结果。再将结果压到数字栈 - 如果没有字符读入了,就从 numStack 中弹出一个数字,那个数字就是结果。(当然弹出后就是空栈了)
为什么这个算法可以运行?
不知道如何说。大概就是当你遇到)
的时候(表达式无误的时候),就已经数字栈至少有 2 个数字,运算符栈至少有一个操作数了,计算的结果再压到数字栈,是用这个数字去顶替这个表达式。所以可以利用这个规律反复求值,最后就为所求了。
画个图就是这样。
遇到数字2
,压到数字栈

)
,弹出 2 个数字,弹出一个运算符,就进行运算,再将结果压到数字栈,就变成这样了。



最后 4 为所求

代码实现
为了方便调试,一般我会先写个REPL。比如是这样的
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
while (true) {
System.out.print(">");
String command = scan.nextLine();
try {
if(command.length() == 0){
continue;
}
System.out.println(Eval(command));
} catch (Exception e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
}
}
就可以在在终端中输入表达式不断调试了。
当然D爷的算法实现也很简单,如果理解了上面的内容很容易写出来的。
public static double Eval(String input) {
String[] tokens = input.split(" ");
Stack<String> ops = new Stack<>();
Stack<Double> vals = new Stack<>();
for (String token : tokens) {
switch (token) {
case "(":
case "\r":
case "\n":
continue;
case "+":
case "-":
case "*":
case "/":
ops.push(token);
break;
case ")":
String op = ops.pop();
Double val1 = vals.pop();
Double val2 = vals.pop();
switch (op) {
case "+":
vals.push(val2 + val1);
break;
case "-":
vals.push(val2 - val1);
break;
case "*":
vals.push(val2 * val1);
break;
case "/":
vals.push(val2 / val1);
}
break;
default:
Double val = Double.valueOf(token);
vals.push(val);
break;
}
}
return vals.pop();
}
总结
- 这四则运算实际上是一个的“解释器”,
input.split(" ")
这句其实是最简单的词法分析(Lexer),将字符串变成分隔成一个个单词元,而后面的代码,D爷用很巧妙的方式完成,解释执行。 - 其实这段代码有挺多问题的(当然是我的问题),比如输入
(2 * 2 3)
结果会是 6 。。。因为没有对表达式的语法进行检查。c语言等编程语言中是会检查这些语法的,那么编程语言是如何做到的呢? - 我们的编程语言中的四则运算(当然除了 Lisp)也没有那么多括号,没有那么多空格的, 对四则表达式的语法求值是和我们学习的数学一样的,比如
1+2*3
会得出7
,那么编程语言是如何做到的呢? - 你可以看这篇文章,实现一个简单的解释器。
参考资料
《算法(第四版)》