Golang 闭包的妙用
起因
群里有朋友讨论了一下go官方sort包的SearchInts方法,代码如下:
func SearchInts(a []int, x int) int {
return Search(len(a), func(i int) bool { return a[i] >= x })
}
其中Search代码如下:
func Search(n int, f func(int) bool) int {
// Define f(-1) == false and f(n) == true.
// Invariant: f(i-1) == false, f(j) == true.
i, j := 0, n
for i < j {
h := int(uint(i+j) >> 1) // avoid overflow when computing h
// i ≤ h < j
if !f(h) {
i = h + 1 // preserves f(i-1) == false
} else {
j = h // preserves f(j) == true
}
}
// i == j, f(i-1) == false, and f(j) (= f(i)) == true => answer is i.
return i
}
看这段代码发现了这样设计的妙处。
将算法和原始数据解耦
可以看出search方法是算法的核心,也就是二分查找,代码也很好的懂,如果数组中间那个值满足f条件,则在左半边查找,否则就在右半边查找。
如果直接这样写:
func SearchInts(a []int, x int) int {
// ......(将二分查找的算法直接写在这里)
}
你会发现,当你需要SearchCharts的时候,你需要再写一次二分查找算法。
很容易想到要把Search算法提炼成一个方法,要满足不同类型,那很容易就会想到interface:
func Search(a []interface{}, x int) int {
switch a.(type){
case int:
// ......(将二分查找的算法直接写在这里)
case string:
// ......(将二分查找的算法换个类型再写一次)
}
}
或者更进一步,把比较函数独立出来,这样switch这一段就不用出现在Search函数里了。
但是这样依然不够:如果我想用自定义的struct怎么办?
也有办法:约定使用者如果传入struct就必须让这个struct实现compare方法,我直接调用这个对象的compare方法
不过这些解决方案都不够优雅,代码复杂难看,对使用者也有心智负担。
看看sort包的实现方法,如果我想对自定义的类型进行排序,需要做什么:
type Mytype struct {
a int
b string
}
func main() {
myData := []Mytype{{1,"one"}, {5,"five"}, {9,"nine"}}
targetVal := 5
i := sort.Search(len(myData), func(i int) bool {
return myData[i].a >= targetVal
})
fmt.Println(i)
// output: 1
}
可以看到,任何一个自定义的类型,或者原生的类型都可以使用这个方法,而且无需额外实现某些接口或方法,使用起来也没有任何心智负担,非常清爽。