简单的四则运算(一)

实现一个简单的四则运算,听起来容易,实际上挺麻烦的。比如是实现符号的优先级,括号是最优先的,其次是乘除,然后再是加减,你要如何实现呢?再比如是给你一串字符,比如是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();
}
复制代码

总结

  1. 这四则运算实际上是一个的“解释器”,input.split(" ")这句其实是最简单的词法分析(Lexer),将字符串变成分隔成一个个单词元,而后面的代码,D爷用很巧妙的方式完成,解释执行。
  2. 其实这段代码有挺多问题的(当然是我的问题),比如输入 (2 * 2 3) 结果会是 6 。。。因为没有对表达式的语法进行检查。c语言等编程语言中是会检查这些语法的,那么编程语言是如何做到的呢?
  3. 我们的编程语言中的四则运算(当然除了 Lisp)也没有那么多括号,没有那么多空格的, 对四则表达式的语法求值是和我们学习的数学一样的,比如1+2*3会得出7,那么编程语言是如何做到的呢?
  4. 你可以看这篇文章,实现一个简单的解释器。

参考资料

《算法(第四版)》

原文在 blog.zhangguojian.com/2018/10/13/…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值