当前位置 主页 > 网站技术 > 代码类 >

    Go 中 slice 的 In 功能实现探索

    栏目:代码类 时间:2019-09-15 14:02

    之前在知乎看到一个问题:为什么 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).Interface() == needle { ...}

    Go 中并非任何类型都可以使用 == 比较的,如果元素中含有 slice 或 map,则可能会报错。

    二分查找

    以遍历确认元素是否存在有个缺点,那就是,如果数组或切片中包含了大量数据,比如 1000000 条数据,即一百万,最坏的情况是,我们要遍历 1000000 次才能确认,时间复杂度 On。

    有什么办法可以降低遍历次数?

    自然而然地想到的方法是二分查找,它的时间复杂度 log2(n) 。但这个算法有前提,需要依赖有序序列。

    于是,第一个要我们解决的问题是使序列有序,Go 的标准库已经提供了这个功能,在 sort 包下。