如何检查x是否在一个数组中,而不遍历整个数组,使用Go?语言中有这样的结构吗?

如在Python中:

if "x" in array: 
  # do something

在Go中没有内置的操作符来做这件事。你需要遍历数组。你可以编写自己的函数来实现,就像这样:

func stringInSlice(a string, list []string) bool {
    for _, b := range list {
        if b == a {
            return true
        }
    }
    return false
}

或者在Go 1.18或更新版本中,你可以使用切片。包含(来自golang.org/x/exp/slices)。

如果你想在不遍历整个列表的情况下检查成员关系,你需要使用map而不是数组或slice,如下所示:

visitedURL := map[string]bool {
    "http://www.google.com": true,
    "https://paypal.com": true,
}
if visitedURL[thisSite] {
    fmt.Println("Already been here.")
}

这段话摘自《Go编程:为21世纪创建应用程序》一书:

Using a simple linear search like this is the only option for unsorted data and is fine for small slices (up to hundreds of items). But for larger slices—especially if we are performing searches repeatedly—the linear search is very inefficient, on average requiring half the items to be compared each time. Go provides a sort.Search() method which uses the binary search algorithm: This requires the comparison of only log2(n) items (where n is the number of items) each time. To put this in perspective, a linear search of 1000000 items requires 500000 comparisons on average, with a worst case of 1000000 comparisons; a binary search needs at most 20 comparisons, even in the worst case.

files := []string{"Test.conf", "util.go", "Makefile", "misc.go", "main.go"}
target := "Makefile"
sort.Strings(files)
i := sort.Search(len(files),
    func(i int) bool { return files[i] >= target })
if i < len(files) && files[i] == target {
    fmt.Printf("found \"%s\" at files[%d]\n", files[i], i)
}

https://play.golang.org/p/UIndYQ8FeW


如果列表包含静态值,则另一种解决方案。

例:从有效值列表中检查有效值:

func IsValidCategory(category string) bool {
    switch category {
    case
        "auto",
        "news",
        "sport",
        "music":
        return true
    }
    return false
}

上面使用sort的例子很接近,但是在字符串的情况下只需使用SearchString:

files := []string{"Test.conf", "util.go", "Makefile", "misc.go", "main.go"}
target := "Makefile"
sort.Strings(files)
i := sort.SearchStrings(files, target)
if i < len(files) && files[i] == target {
    fmt.Printf("found \"%s\" at files[%d]\n", files[i], i)
}

https://golang.org/pkg/sort/#SearchStrings


另一种选择是使用地图作为集合。你只使用键,并让值为布尔值,总是true。然后,您可以轻松地检查映射是否包含键。如果你需要一个集合的行为,这很有用,如果你多次添加一个值,它只在集合中出现一次。

这是一个简单的例子,我添加随机数作为键映射。如果相同的数字生成了不止一次,那也没关系,它只会在最终地图中出现一次。然后我使用一个简单的if检查来查看一个键是否在映射中。

package main

import (
    "fmt"
    "math/rand"
)

func main() {
    var MAX int = 10

    m := make(map[int]bool)

    for i := 0; i <= MAX; i++ {
        m[rand.Intn(MAX)] = true
    }

    for i := 0; i <= MAX; i++ {
        if _, ok := m[i]; ok {
            fmt.Printf("%v is in map\n", i)
        } else {
            fmt.Printf("%v is not in map\n", i)
        }
    }
}

这是在围棋场上


我也有一个类似的问题,并决定尝试一下这篇文章中的一些建议。

我已经对3种查找类型的最佳和最差情况进行了基准测试:

使用地图 使用列表 使用switch语句

下面是函数代码:

func belongsToMap(lookup string) bool {
list := map[string]bool{
    "900898296857": true,
    "900898302052": true,
    "900898296492": true,
    "900898296850": true,
    "900898296703": true,
    "900898296633": true,
    "900898296613": true,
    "900898296615": true,
    "900898296620": true,
    "900898296636": true,
}
if _, ok := list[lookup]; ok {
    return true
} else {
    return false
}
}


func belongsToList(lookup string) bool {
list := []string{
    "900898296857",
    "900898302052",
    "900898296492",
    "900898296850",
    "900898296703",
    "900898296633",
    "900898296613",
    "900898296615",
    "900898296620",
    "900898296636",
}
for _, val := range list {
    if val == lookup {
        return true
    }
}
return false
}

func belongsToSwitch(lookup string) bool {
switch lookup {
case
    "900898296857",
    "900898302052",
    "900898296492",
    "900898296850",
    "900898296703",
    "900898296633",
    "900898296613",
    "900898296615",
    "900898296620",
    "900898296636":
    return true
}
return false
}

最好的情况是选择列表中的第一项,最坏的情况是使用不存在的值。

以下是调查结果:

BenchmarkBelongsToMapWorstCase-4         2000000           787 ns/op
BenchmarkBelongsToSwitchWorstCase-4     2000000000           0.35 ns/op
BenchmarkBelongsToListWorstCase-4       100000000           14.7 ns/op
BenchmarkBelongsToMapBestCase-4          2000000           683 ns/op
BenchmarkBelongsToSwitchBestCase-4      100000000           10.6 ns/op
BenchmarkBelongsToListBestCase-4        100000000           10.4 ns/op

Switch一路胜出,最坏的情况比最好的情况快得多。

地图是最差的,列表更接近开关。

所以寓意是: 如果你有一个静态的,相当小的列表,switch语句是最好的方法。


这是我所能得到的最接近Python的“in”操作符的自然感觉。您必须定义自己的类型。然后,您可以通过添加像“has”这样的方法来扩展该类型的功能,该方法的行为与您所希望的一样。

package main

import "fmt"

type StrSlice []string

func (list StrSlice) Has(a string) bool {
    for _, b := range list {
        if b == a {
            return true
        }
    }
    return false
}

func main() {
    var testList = StrSlice{"The", "big", "dog", "has", "fleas"}

    if testList.Has("dog") {
        fmt.Println("Yay!")
    }
}

我有一个实用程序库,其中我为几种类型的切片定义了一些常见的东西,比如那些包含整数或我自己的其他结构的切片。

是的,它在线性时间内运行,但这不是重点。重点是询问和学习Go拥有和没有的通用语言结构。这是一个很好的练习。这个答案是傻还是有用取决于读者。


在Go 1.18+中,您现在可以声明泛型Contains函数,该函数也在实验性slice函数中实现。它适用于任何类似的类型

func Contains[T comparable](arr []T, x T) bool {
    for _, v := range arr {
        if v == x {
            return true
        }
    }
    return false
}

像这样使用它:

if Contains(arr, "x") {
    // do something
}
// or
if slices.Contains(arr, "x") {
    // do something
}

我在这里找到的