之前在知乎看到一个问题:为什么 Golang 没有像 Python 中 in 一样的功能?于是,搜了下这个问题,发现还是有不少人有这样的疑问。
今天来谈谈这个话题。
in 是一个很常用的功能,有些语言中可能也称为 contains,虽然不同语言的表示不同,但基本都是有的。不过可惜的是,Go 却没有,它即没有提供类似 Python 操作符 in,也没有像其他语言那样提供这样的标准库函数,如 PHP 中 in_array。
Go 的哲学是追求少即是多。我想或许 Go 团队觉得这是一个实现起来不足为道的功能吧。
为何说微不足道?如果要自己实现,又该如何做呢?
我所想到的有三种实现方式,一是遍历,二是 sort 的二分查找,三是 map 的 key 索引。
本文相关源码已经上传在我的 github 上,poloxue/gotin。
遍历
遍历应该是我们最容易想到的最简单的实现方式。
示例如下:
func InIntSlice(haystack []int, needle int) bool {
for _, e := range haystack {
if e == needle {
return true
}
}
return false
}
上面演示了如何在一个 []int 类型变量中查找指定 int 是否存在的例子,是不是非常简单,由此我们也可以感受到我为什么说它实现起来微不足道。
这个例子有个缺陷,它只支持单一类型。如果要支持像解释语言一样的通用 in 功能,则需借助反射实现。
代码如下:
func In(haystack interface{}, needle interface{}) (bool, error) {
sVal := reflect.ValueOf(haystack)
kind := sVal.Kind()
if kind == reflect.Slice || kind == reflect.Array {
for i := 0; i < sVal.Len(); i++ {
if sVal.Index(i).Interface() == needle {
return true, nil
}
}
return false, nil
}
return false, ErrUnSupportHaystack
}
为了更加通用,In 函数的输入参数 haystack 和 needle 都是 interface{} 类型。
简单说说输入参数都是 interface{} 的好处吧,主要有两点,如下:
其一,haystack 是 interface{} 类型,使 in 支持的类型不止于 slice,还包括 array。我们看到,函数内部通过反射对 haystack 进行了类型检查,支持 slice(切片)与 array(数组)。如果是其他类型则会提示错误,增加新的类型支持,如 map,其实也很简单。但不推荐这种方式,因为通过 _, ok := m[k] 的语法即可达到 in 的效果。
其二,haystack 是 interface{},则 []interface{} 也满足要求,并且 needle 是 interface{}。如此一来,我们就可以实现类似解释型语言一样的效果了。
怎么理解?直接示例说明,如下:
gotin.In([]interface{}{1, "two", 3}, "two")
haystack 是 []interface{}{1, "two", 3},而且 needle 是 interface{},此时的值是 "two"。如此看起来,是不是实现了解释型语言中,元素可以是任意类型,不必完全相同效果。如此一来,我们就可以肆意妄为的使用了。
但有一点要说明,In 函数的实现中有这样一段代码:
if sVal.Index(i).Int