Go 是否有类似于 Python 的“if x in”结构?[复制]

Does Go have "if x in" construct similar to Python? [duplicate]

提问人: 提问时间:3/10/2013 最后编辑:Amal Thundiyil 更新时间:8/13/2023 访问量:432612

问:

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

就像在 Python 中一样:

if "x" in array: 
  # do something
if语句

评论

12赞 Daniel Shaulov 3/10/2013
AFAIK,在围棋中没有简写。在内部,python 也会遍历数组,这是没有办法的。
9赞 Dave C 4/17/2015
顺便说一句,一个重要的注意事项是,没有办法做到这一点(正如你所要求的那样)“[w]ithout遍历整个数组”。明确(或在函数后面)使此类循环有助于使代码在做什么更加明显。我的印象是,也许你认为 Python 正在做一些快速/神奇的事情。AFAIK 它不是。使循环显式有助于使编写者(和所有读取者)了解并考虑其他实现(例如映射)。strings.Indexin array:
8赞 Roberto Alsina 6/16/2017
但是,如果集合中的“x”确实非常快。
17赞 Migwell 1/29/2019
我不认为我们要求一种程序化的快速方法,我们只是要求一种简洁的方法(但仍然没有......
0赞 YaMiN 10/24/2023
@RobertoAlsina 一套,这也是 Go 的不足。

答:

545赞 andybalholm 3/10/2013 #1

在 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)。

如果您希望能够在不遍历整个列表的情况下检查成员资格,则需要使用映射而不是数组或切片,如下所示:

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

评论

7赞 Allen 5/13/2015
有没有办法在不指定类型的情况下做到这一点?假设我只想要一个通用的 needleInHaystack(needle, haystack) 函数,而没有针对每种类型的单独方法
39赞 andybalholm 5/13/2015
它可以使用 reflect 包来完成,但它的效率相当低(可能和你用 Python 等动态语言编写它一样慢)。除此之外,没有。当人们说 Go 没有泛型时,这就是他们的意思。
5赞 RisingSun 11/13/2015
任何偶然发现这个答案的人都必须注意,你不能对地图进行排序。使用 go 地图的一大缺点。
4赞 kumarharsh 2/24/2016
您也无法在 Javascript 中对地图(对象)进行排序。这是一个 v8 错误,对象按字母顺序返回值。
10赞 Hejazzman 3/29/2018
地图在大多数语言中都没有排序 - 这是地图数据结构(HashMap)的正常做法。
57赞 AlexTT 10/25/2015 #2

这是《Programming in Go: Creating Applications for the 21st Century》一书中的一段话:

使用像这样的简单线性搜索是未排序的唯一选择 数据,适用于小切片(最多数百个项目)。但是对于 较大的切片(尤其是当我们重复执行搜索时), 线性搜索效率非常低,平均需要一半的项目 每次进行比较。

Go 提供了一种排序。Search() 方法,使用二进制搜索 algorithm:这只需要比较 log2(n) 个项目(其中 n 是项目数)。从这个角度来看,一个 线性搜索 1000000 个项目平均需要 500000 次比较, 最坏的情况是 1000000 次比较;二进制搜索需要 大多数 20 个比较,即使在最坏的情况下也是如此。

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

评论

13赞 christian 10/18/2017
这只有在您进行重复搜索时才有意义。否则,排序和二进制搜索的复杂度为 n*log(n)*log(n),而线性搜索的复杂度仅为 n。
12赞 pomo_mondreganto 6/17/2020
它实际上只是 ,因为它是两个随之而来的独立操作n*log(n) + log(n)
164赞 sebest 11/26/2015 #3

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

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

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

评论

25赞 TeAmEr 8/26/2016
是的,如果您的“有效值”来自数据库怎么办?
0赞 piggybox 12/15/2018
是的,这很整洁,但前提是可以提前定义这些值。
5赞 anilech 3/29/2019
@RonanDejhero然后我可以使用 WHERE :myValue IN(子查询):)
4赞 piggybox 7/3/2019
与顶级答案相比,这很整洁
0赞 Mathyou 4/11/2021
正如其他评论所提到的,这对于值为常量的情况非常有用!否则,我推荐接受的答案。
29赞 Robert Weber 1/5/2016 #4

上面使用 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

评论

3赞 cytinus 9/26/2017
这个答案看起来像是下面答案的信息量较少的复制粘贴,它发生在这个答案之前。
0赞 akim 9/27/2017
@cytinus 你指的是什么答案?这是我看到的唯一一个基于.sort.SearchStrings
1赞 Xeoncross 1/31/2018
我在一大片重复搜索时获得了 100 倍的加速。
8赞 nobled 1/7/2016 #5

另一种选择是将地图用作集合。您只使用键,并将值设置为始终为真的布尔值。然后,您可以轻松检查地图是否包含密钥。如果您需要集合的行为,这将很有用,如果多次添加一个值,则它只会在集合中出现一次。

这是一个简单的示例,我将随机数作为键添加到地图中。如果多次生成相同的数字,则没关系,它只会在最终地图中出现一次。然后,我使用一个简单的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)
        }
    }
}

这是在移动游乐场上

46赞 Igor 10/9/2018 #6

刚刚有一个类似的问题,并决定尝试此线程中的一些建议。

我已经对 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 语句是要走的路。

评论

0赞 w00t 4/9/2019
我不知道 Go 是否优化了这种情况,但是如果您将列表/映射的初始化移到测试函数之外会有所不同吗?
0赞 Michael Draper 4/18/2019
我很好奇这种比较将如何与排序列表进行比较,以及排序是否值得
0赞 Thomas Sauvajon 2/10/2020
而不是在 switch 语句中呢?它能使它更快吗?:,
4赞 Gwyneth Llewelyn 7/22/2020
没有什么比提供解决方案并使用基准测试将其与最“明显”的其他解决方案进行比较更好的了——用数据而不是猜测来支持您的建议!谢谢 - 我会坚持使用开关,因为我有固定的、静态的检查量......
3赞 Duncan Gener8 3/28/2022
这个基准存在严重缺陷。您正在初始化要在基准测试的相同方法中搜索的结构。我重新实现了这个,并将这些列表和映射的初始化放入方法中。当不包括映射初始化或切片初始化时,当您在要搜索的结构中有 10 个键时,搜索地图的速度会快两倍以上。随着条目数量的增加,切片的这种差异只会变得更糟。func Bench...
15赞 IcarusNM 4/24/2020 #7

这尽可能接近 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 有哪些通用语言结构,以及没有哪些通用语言结构。这是一个很好的练习。这个答案是愚蠢的还是有用的取决于读者。

8赞 go je jo 7/19/2022 #8

在 Go 1.18+1.21+ 中,现在可以声明通用函数或将内置的 slice 包导入到实验性切片包中。它适用于任何可比类型Contains

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
}

或通过导入

// func[S ~[]E, E comparable](s S, v E) bool
if slices.Contains(arr, "x") {
    // do something
}

我在这里找到的

2赞 gaozhidf 2/13/2023 #9

尝试 : https://github.com/samber/lo#containslo

present := lo.Contains[int]([]int{0, 1, 2, 3, 4, 5}, 5)