了解了下go生态内的表达式计算库,govaluate评价比较高,目前github有2300多个star
用这个库,比较容易解决某些场景下复杂规则计算的需求
相关使用参考Go 每日一库之 govaluate,以下是对该篇内容的实践与记录
demo1–基础款
使用govaluate计算表达式只需要两步:
调用NewEvaluableExpression()将表达式转为一个表达式对象(一个EvaluableExpression类型的结构体);
调用表达式对象的Evaluate方法,传入参数,返回表达式的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package main
import ( "fmt" "github.com/Knetic/govaluate" )
func main() {
expr, err := govaluate.NewEvaluableExpression("10 >= 0") if err != nil { fmt.Println("syntax error:", err) }
result, err := expr.Evaluate(nil) if err != nil { fmt.Println("evaluate error:", err) }
fmt.Println(result) }
|
输出:
使用govaluate计算10 >= 0
的值,该表达式不需要参数,所以传给Evaluate()方法nil值。
当然这个例子并不实用。但有时并不知道需要计算的表达式的所有信息,甚至都不知道表达式的结构,这时govaluate的作用就体现出来了。
如果govaluate.NewEvaluableExpression()的表达式是不合法的,则err将不为nil,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package main
import ( "fmt" "github.com/Knetic/govaluate" )
func main() {
expr, err := govaluate.NewEvaluableExpression("10 !>= 0") if err != nil { fmt.Println("syntax error:", err) }
fmt.Println("expr is:", expr)
result, err := expr.Evaluate(nil) if err != nil { fmt.Println("evaluate error:", err) }
fmt.Println(result) }
|
输出:
1 2 3 4 5 6 7 8 9
| syntax error: Invalid token: '!>=' expr is: <nil> panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x104d463f0]
goroutine 1 [running]: main.main() /Users/fliter/go/src/shuang/govaluatee/a.go:17 +0x160 exit status 2
|
demo2–带参数
支持在表达式中使用参数,调用表达式对象的Evaluate()方法
时通过map[string]interface{}类型将参数传入运算。
其中map的键为参数名,值为参数值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| package main
import ( "fmt" "github.com/Knetic/govaluate" )
func main() {
expr, _ := govaluate.NewEvaluableExpression("foo > 0") parameters := make(map[string]interface{}) parameters["foo"] = -1 result, _ := expr.Evaluate(parameters) fmt.Println(result)
expr, _ = govaluate.NewEvaluableExpression("(requests_made * requests_succeeded / 100) >= 90") parameters = make(map[string]interface{}) parameters["requests_made"] = 100 parameters["requests_succeeded"] = 80 result, _ = expr.Evaluate(parameters) fmt.Println(result)
expr, _ = govaluate.NewEvaluableExpression("(mem_used / total_mem) * 100") parameters = make(map[string]interface{}) parameters["total_mem"] = 1024 parameters["mem_used"] = 512 result, _ = expr.Evaluate(parameters) fmt.Println(result) }
|
输出:
demo3–特殊标识符的处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package main
import ( "fmt" "github.com/Knetic/govaluate" )
func main() {
expr, err1 := govaluate.NewEvaluableExpression("response-time < 100") if err1 != nil { fmt.Println("err1 is:", err1) } parameters := make(map[string]interface{}) parameters["response-time"] = 80
result, err2 := expr.Evaluate(parameters) if err2 != nil { fmt.Println("err2 is:", err2) }
fmt.Println(result)
}
|
输出:
1 2
| err2 is: No parameter 'response' found. <nil>
|
如果NewEvaluableExpression()中的表达式是如同a+b>100
,或x-y<50
等情况,又想将a+b
作为一个整体参数,通过parameters一次性传入,因为Go 代码中标识符中不能出现-、+、$
等符号,故而需要转义处理。
主要方式有两种:
- 将名称用[和]包裹起来,例如[response-time];
- 使用\将紧接着下一个的字符转义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package main
import ( "fmt" "github.com/Knetic/govaluate" )
func main() {
expr, _ := govaluate.NewEvaluableExpression("[response-time] < 100") parameters := make(map[string]interface{}) parameters["response-time"] = 80 result, _ := expr.Evaluate(parameters) fmt.Println(result)
expr, _ = govaluate.NewEvaluableExpression("response\\-time < 100") parameters = make(map[string]interface{}) parameters["response-time"] = 80 result, _ = expr.Evaluate(parameters) fmt.Println(result) }
|
输出:
而如果将response和time作为两个参数,则无需做处理,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package main
import ( "fmt" "github.com/Knetic/govaluate" )
func main() {
expr, err1 := govaluate.NewEvaluableExpression("response-time < 100") if err1 != nil { fmt.Println("err1 is:", err1) } parameters := make(map[string]interface{}) parameters["response"] = 80 parameters["time"] = 10
result, err2 := expr.Evaluate(parameters) if err2 != nil { fmt.Println("err2 is:", err2) }
fmt.Println(result)
}
|
输出:
demo4–一次“编译”,多次运行
使用带参数的表达式,可实现一个表达式的一次“编译”,多次运行。只需要调用编译返回的表达式对象的Evaluate()方法即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package main
import ( "fmt" "github.com/Knetic/govaluate" )
func main() { expr, _ := govaluate.NewEvaluableExpression("a + b") parameters := make(map[string]interface{}) parameters["a"] = 1 parameters["b"] = 2 result, _ := expr.Evaluate(parameters) fmt.Println(result)
parameters = make(map[string]interface{}) parameters["a"] = 10 parameters["b"] = 20 result, _ = expr.Evaluate(parameters) fmt.Println(result) }
|
输出:
demo5–使用 自定义函数
如果仅仅只能进行常规的算数和逻辑运算,govaluate的功能则会大打折扣。govaluate还提供了强大的自定义函数功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package main
import ( "fmt" "github.com/Knetic/govaluate" )
func main() {
functions := map[string]govaluate.ExpressionFunction{ "strlen": func(args ...interface{}) (interface{}, error) { length := len(args[0].(string)) return length, nil }, }
exprString := "strlen('shuang_length')"
expr, _ := govaluate.NewEvaluableExpressionWithFunctions(exprString, functions) result, _ := expr.Evaluate(nil) fmt.Println(result) }
|
输出:
函数可以接受任意数量的参数,且可处理嵌套函数调用的问题。所以能写出类似下面这种复杂的表达式:
1 2 3
| sqrt(x1 ** y1, x2 ** y2)
max(someValue, abs(anotherValue), 10 * lastValue)
|
demo6–访问器
处理结构体时,一般用不到
demo7–支持的操作和类型
govaluate支持的操作和类型与 Go 语言有些不同:一方面govaluate中的类型和操作不如 Go 丰富,另一方面govaluate也对一些操作进行了扩展。
算数、比较和逻辑运算:
常量:
- 数字常量,govaluate中将数字都作为 64 位浮点数处理;
- 字符串常量,注意在govaluate中,字符串用单引号’;
- 日期时间常量,格式与字符串相同,govaluate会尝试自动解析字符串是否是日期,只支持 RFC3339、ISO8601等有限的格式;
- 布尔常量:true、false。
其他:
- 圆括号可以改变计算优先级;
- 数组定义在()中,每个元素之间用,分隔,可以支持任意的元素类型,如(1, 2, ‘foo’)。实际上在govaluate中数组是用[]interface{}来表示的;
- 三目运算符:? :
在下面代码中,govaluate会先将2014-01-02和2014-01-01 23:59:59转为time.Time类型,然后再比较大小:
1 2 3 4 5 6 7 8 9 10 11 12
| package main
import ( "fmt" "github.com/Knetic/govaluate" )
func main() { expr, _ := govaluate.NewEvaluableExpression("'2014-01-02' > '2014-01-01 23:59:59'") result, _ := expr.Evaluate(nil) fmt.Println(result) }
|
输出:
demo8–错误处理
在创建表达式对象和表达式求值这两个操作中都可能产生错误:在生成表达式对象时,如果表达式有语法错误,则返回错误;表达式求值时,如果传入的参数不合法,或者某些参数缺失,或者访问结构体中不存在的字段都会报错。
demo部分代码
↑
BTC Address:3NNxkM6ez7szsUAgTnK2VaF949LoGmXuBs