Golang 算术表达式解析

前言

项目中需要用到解析算术表达式的模块,简单来说,就是 eval 函数,但并没有相应的标准库有类似的实现,所以想使用栈实现一个简单的算术表达式解析的模块。

首先,需要明确的是运算符优先级,这里只需要实现加减乘除,括号,and,or,不等号相关的符号即可。对应的优先级如下

  1. 括号
  2. 类型运算符(前缀 + -)
  3. 乘,除
  4. 加,减
  5. 大于,小于,不大于,不小于
  6. 不等于,等于
  7. and
  8. or

程序需要做的就是把优先级相关的东西去掉,然后转化成后缀表达式,即逆波兰表达式。

处理流程

需要两个栈,resultops 前者用于保存结果,后者用于暂存操作符。

  1. 如果遇到操作数,则 result 入栈
  2. 如果遇到操作符,则检查 ops 栈顶元素优先级
  3. 如果其优先级不低于当前操作符(左括号除外),则弹出 ops 栈顶元素并压入 result
  4. 重复此过程直到 ops 栈顶元素优先级小于当前操作符,或为左括号,或者 ops 为空
  5. 将当前操作符压入 ops
  6. 如果遇到左括号,直接压入 ops
  7. 如果遇到右括号,则将 ops 中元素弹出,直到遇到左括号为止。左括号只弹出栈而不输出
  8. 表达式处理完毕,则将栈中元素依次压入 result

例如,1 * 2 + 3 * 4 就变成 1 2 * 3 4 * +。后缀表达式的计算比中缀要容易得多,也不需要关心优先级,只需要简单的使用栈处理下就好。这里需要注意的是,由于比较运算法需要关注左右操作数的位置,所以实际运算的时候需要注意使用栈弹出之后位置就发生了交换。

上面的逻辑没有处理 + - 的类型前缀的情况。如果需要进行相关处理,则需要判断符号前面是不是数,如果是数的话,则认为当前的符号是二元操作符。因为一元操作符比二元操作符的优先级高,所以也可以将其转化为二元操作符。例如 -1 + 1 则可转变为 -1 * 1 + 1

代码实现

首先用正则解析出合法的输入

1
var legalArthmeticRegex string = `^(\d+(\.\d+)?|\+|\-|\*|\/|and|or|\(|\)|==|>=|<=|!=|>|<)+$`

然后需要实现一个简单的 stack,Golang 里面竟然没有实现 stack 这样的东西啊。

然后解析出来之后,将其转为逆波兰式,这里需要用到 stack 来帮助解析。解析出来之后的计算就很简单了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func calculateRPN(tokens []string) (result float64, err error) {
stack := stacks.NewStack()
for _, token := range tokens {
if isNumber(token) {
stack.Push(token)
continue
}

if stack.Size() < 2 {
err = fmt.Errorf("reverse polish notation is wrong")
return
}
b := stack.Pop()
a := stack.Pop()
result, err = calculate(a.(string), b.(string), token)
if err != nil {
return
}
stack.Push(fmt.Sprintf("%.2f", result))
}
return
}

最后输出的结果是 float64 的类型,布尔类型的结果只能以是否为 0 来进行判断。

高级玩法

使用后缀表达式,没办法解决例如 and or 的优化,只能简单的处理计算而已。

小结

其实真正写的时间就一个下午而已,使用栈来实现的算法也是一搜一大堆。写下来发现其实感觉编译原理挺好玩的。

参考

  1. gox
  2. C++ 运算符优先级
  3. github 上一个类似的 Golang 项目 goexpression

Golang 算术表达式解析
http://yoursite.com/2016/10/04/golang-expression/
Author
Shing
Posted on
October 4, 2016
Licensed under