包含切片的方法

Contains method for a slice

提问人:vosmith 提问时间:5/8/2012 最后编辑:vosmith 更新时间:9/20/2023 访问量:662275

问:

有没有类似于 Go 中的方法而不必对切片中的每个元素进行搜索?slice.contains(object)

切片

评论

8赞 Rodrigo 3/29/2016
github.com/forestgiant/sliceutil
2赞 Igor A. Melekhine 5/3/2022
尽量不要像您提供@Rodrigo那样使用第三方软件包来完成此类工作。它使你的代码变得笨重和脆弱
0赞 Omkesh Sajjanwar 7/12/2022
go.dev/play/p/B1qGeOLI9Na
0赞 Jeff Learman 7/24/2023
在不访问每个元素的情况下搜索切片是不可能的,因此它始终是 O(N)。如果需要可伸缩性,切片是错误的数据结构。

答:

19赞 Matt K 5/8/2012 #1

如果对切片进行了排序,则在排序中实现了二进制搜索。

337赞 Mostafa 5/8/2012 #2

不,这样的方法不存在,但写起来很琐碎:

func contains(s []int, e int) bool {
    for _, a := range s {
        if a == e {
            return true
        }
    }
    return false
}

如果查找是代码的重要组成部分,则可以使用地图,但地图也有成本。

评论

526赞 Eloff 7/13/2014
实际上,这并不是一件容易的事,因为你必须为你使用的每种类型编写一个,而且因为没有重载,所以你必须以不同的方式命名每个函数,就像在 C 中一样。 append() 可以通用地工作,因为它具有特殊的运行时支持。出于同样的原因,泛型包含很有用,但实际上泛型解决方案只是语言中的泛型支持。
2赞 Ory Band 1/8/2015
@Alex Lockwood,这真的适用于接口吗?
235赞 tothemario 10/20/2016
琐碎 == 7 行代码,包括 1 个循环、1 个分支 if 语句和 1 个比较?我想我在这里错过了一些东西......
91赞 Surya 10/17/2019
但是为什么不把这些添加到go核心本身呢?
22赞 Akito 3/9/2022
如果 Go 在这方面像 C 一样痛苦,那还有什么意义......如果如此微不足道,将其添加到标准库中应该是不言自明的。contains
365赞 tux21b 5/8/2012 #3

Mostafa 已经指出,这样的方法编写起来很简单,mkb 给了你一个提示,让你使用排序包中的二进制搜索。但是,如果您要进行大量此类包含检查,也可以考虑使用地图。

使用惯用语检查特定地图键是否存在是微不足道的。由于你对这个值不感兴趣,你也可以创建一个 例如。在这里使用空的优点是它不需要任何额外的空间,并且 Go 的内部地图类型针对这种值进行了优化。因此,是围棋世界中套装的热门选择。value, ok := yourmap[key]map[string]struct{}struct{}map[string] struct{}

评论

33赞 tux21b 5/8/2012
另请注意,您必须写入以获取空结构的值,以便在要添加元素时可以将其传递给映射。试试吧,如果遇到任何问题,请随时提问。如果您更容易理解,您也可以使用 Mostafa 的解决方案(除非您有大量数据)。struct{}{}
19赞 Igor Petrov 5/23/2017
解决方案很简单,这是真的。但是,在运行时中添加这些基本功能需要什么呢?我在 github 上的 Go 存储库中没有发现此类问题。这很可悲,很奇怪。
4赞 vadasambar 9/12/2019
与 相比如何。 似乎是一个黑客,尤其是初始化一个空结构map[string] boolmap[string] struct{}map[string] struct{}struct {}{}
14赞 jcollum 4/22/2020
@IgorPetrov同意,我很惊讶这样的基本功能还没有出现在运行时中。
33赞 Snowcrash 5/25/2021
荒谬的是,你必须自己添加这个。
21赞 holys 12/3/2014 #4

而不是使用 ,可能是更好的解决方案。slicemap

简单示例:

package main

import "fmt"


func contains(slice []string, item string) bool {
    set := make(map[string]struct{}, len(slice))
    for _, s := range slice {
        set[s] = struct{}{}
    }

    _, ok := set[item] 
    return ok
}

func main() {

    s := []string{"a", "b"}
    s1 := "a"
    fmt.Println(contains(s, s1))

}

http://play.golang.org/p/CEG6cu4JTf

评论

55赞 Roland Illig 10/27/2015
在当前形式中,此代码没有任何好处,因为如果您只打算使用一次,那么从切片构建地图是没有意义的。— 为了有用,此代码应该提供一个执行所有准备的函数。之后,查询地图既简单又高效。sliceToMap
1赞 Junaed 5/27/2022
您正在通过迭代切片来创建地图,这是双倍的工作量,更多的代码,更低的效率
5赞 Ethan Kennedy 9/1/2016 #5

您可以使用 reflect 包来迭代具体类型为 slice 的接口:

func HasElem(s interface{}, elem interface{}) bool {
    arrV := reflect.ValueOf(s)

    if arrV.Kind() == reflect.Slice {
        for i := 0; i < arrV.Len(); i++ {

            // XXX - panics if slice element points to an unexported struct field
            // see https://golang.org/pkg/reflect/#Value.Interface
            if arrV.Index(i).Interface() == elem {
                return true
            }
        }
    }

    return false
}

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

评论

7赞 Justin Ohms 7/2/2019
当然,您可以使用 reflect 包,但仅仅因为您可以使用,并不意味着您应该这样做。反射非常昂贵。
1赞 Alex Carruthers 5/14/2021
在实际应用程序代码中,不,您不应该这样做。它很贵。但是,对于单元测试来说,这并不重要,而且非常有用。
4赞 JonPen 9/20/2017 #6

不确定这里是否需要泛型。你只需要一个你想要的行为的合同。如果您希望自己的对象在集合中表现自己,例如,通过重写 Equals() 和 GetHashCode(),执行以下操作并不比在其他语言中必须执行的操作多。

type Identifiable interface{
    GetIdentity() string
}

func IsIdentical(this Identifiable, that Identifiable) bool{
    return (&this == &that) || (this.GetIdentity() == that.GetIdentity())
}

func contains(s []Identifiable, e Identifiable) bool {
    for _, a := range s {
        if IsIdentical(a,e) {
            return true
        }
    }
    return false
}

评论

1赞 George 5/17/2019
“is not more than what you would have to do to do in other languages” 并不是真的 - 例如,在 C# 中是实现的,所以你只需要为这项工作实现。Contains()List<T>Equals()
3赞 Alexander van Trijffel 4/8/2018 #7

如果使用地图根据键查找物品不可行,则可以考虑使用goderive工具。Goderive 生成 contains 方法的特定类型实现,使代码既可读又高效。

例;

type Foo struct {
    Field1 string
    Field2 int
} 

func Test(m Foo) bool {
     var allItems []Foo
     return deriveContainsFoo(allItems, m)
}

要生成 deriveContainsFoo 方法,请执行以下操作:

  • 使用go get -u github.com/awalterschulze/goderive
  • 在工作区文件夹中运行goderive ./...

此方法将为 deriveContains 生成:

func deriveContainsFoo(list []Foo, item Foo) bool {
    for _, v := range list {
        if v == item {
            return true
        }
    }
    return false
}

Goderive 还支持其他一些有用的辅助方法,以在 go 中应用函数式编程风格。

9赞 Jim Hsiang 11/4/2018 #8
func Contain(target interface{}, list interface{}) (bool, int) {
    if reflect.TypeOf(list).Kind() == reflect.Slice || reflect.TypeOf(list).Kind() == reflect.Array {
        listvalue := reflect.ValueOf(list)
        for i := 0; i < listvalue.Len(); i++ {
            if target == listvalue.Index(i).Interface() {
                return true, i
            }
        }
    }
    if reflect.TypeOf(target).Kind() == reflect.String && reflect.TypeOf(list).Kind() == reflect.String {
        return strings.Contains(list.(string), target.(string)), strings.Index(list.(string), target.(string))
    }
    return false, -1
}
27赞 Henrik Aasted Sørensen 9/20/2019 #9

排序包提供构建基块,前提是您的切片已排序或您愿意对其进行排序。

input := []string{"bird", "apple", "ocean", "fork", "anchor"}
sort.Strings(input)

fmt.Println(contains(input, "apple")) // true
fmt.Println(contains(input, "grow"))  // false

...

func contains(s []string, searchterm string) bool {
    i := sort.SearchStrings(s, searchterm)
    return i < len(s) && s[i] == searchterm
}

SearchString承诺返回 ,因此对其进行检查会显示字符串是否包含排序后的切片。the index to insert x if x is not present (it could be len(a))

评论

2赞 plesiv 5/10/2020
就时间而言,常规搜索是,此解决方案使它成为 .O(n)O(n*log(n))
2赞 Henrik Aasted Sørensen 5/10/2020
@plesiv这是一个二进制搜索,AFAICS。这难道不会使它成为 O(log n) 吗?
9赞 plesiv 5/25/2020
是的,binary-search 和函数是 ,但整体方法是由于排序。containsO(log(n))O(n*log(n))
1赞 Zamicol 7/14/2022
@plesiv 是的,对于单次搜索是正确的,但如果搜索多次,比如 n 次,那么它是 O(n + n * log(n)) vs O(n * n)。亨里克的回答显示搜索不止一次。
1赞 Roy O'Young 11/15/2019 #10

围棋风格:

func Contains(n int, match func(i int) bool) bool {
    for i := 0; i < n; i++ {
        if match(i) {
            return true
        }
    }
    return false
}


s := []string{"a", "b", "c", "o"}
// test if s contains "o"
ok := Contains(len(s), func(i int) bool {
    return s[i] == "o"
})

评论

3赞 Croolman 11/15/2019
这不能回答问题,也不能提供其他信息。
-3赞 chopper24 2/7/2020 #11

它可能被认为有点“hacky”,但根据切片的大小和内容,您可以将切片连接在一起并进行字符串搜索。

例如,您有一个包含单个单词值的切片(例如“yes”、“no”、“maybe”)。这些结果将追加到切片中。如果要检查此切片是否包含任何“可能”结果,可以使用

exSlice := ["yes", "no", "yes", "maybe"]
if strings.Contains(strings.Join(exSlice, ","), "maybe") {
  fmt.Println("We have a maybe!")
}

这实际上取决于切片的大小和其成员的长度。对于大切片或长值,可能存在性能或适用性问题,但对于有限大小和简单值的较小切片,它是实现所需结果的有效单行。

评论

0赞 Raees Iqbal 4/22/2020
不适用于元素具有相似文本但不完全相同的情况exSlice := ["yes and no", "maybe", "maybe another"]
2赞 Brent Bradburn 6/21/2020
对于实现快速而肮脏的单行解决方案来说,这是一种相当不错的方法。您只需要一个明确的分隔符(可以是逗号),并执行额外的工作来将两个字符串括起来:和","+strings.Join(exSlice,",")+","",maybe,"
7赞 Bill Burdick 8/5/2020 #12

我认为比.map[x]boolmap[x]struct{}

为不存在的项目编制地图索引将返回 。所以,你可以说 。false_, ok := m[X]m[X]

这样可以很容易地在表达式中嵌套包含测试。

评论

1赞 Seraf 2/24/2023
这很好,但请记住,struct{} 的空间复杂度为 0,而 bool 的空间复杂度更高
-1赞 glassonion1 2/25/2021 #13

我使用 reflect 包创建了以下 Contains 函数。 此函数可用于各种类型,如 int32 或 struct 等。

// Contains returns true if an element is present in a slice
func Contains(list interface{}, elem interface{}) bool {
    listV := reflect.ValueOf(list)

    if listV.Kind() == reflect.Slice {
        for i := 0; i < listV.Len(); i++ {
            item := listV.Index(i).Interface()

            target := reflect.ValueOf(elem).Convert(reflect.TypeOf(item)).Interface()
            if ok := reflect.DeepEqual(item, target); ok {
                return true
            }
        }
    }
    return false
}

contains 函数的用法如下

// slice of int32
containsInt32 := Contains([]int32{1, 2, 3, 4, 5}, 3)
fmt.Println("contains int32:", containsInt32)

// slice of float64
containsFloat64 := Contains([]float64{1.1, 2.2, 3.3, 4.4, 5.5}, 4.4)
fmt.Println("contains float64:", containsFloat64)


// slice of struct
type item struct {
    ID   string
    Name string
}
list := []item{
    item{
        ID:   "1",
        Name: "test1",
    },
    item{
        ID:   "2",
        Name: "test2",
    },
    item{
        ID:   "3",
        Name: "test3",
    },
}
target := item{
    ID:   "2",
    Name: "test2",
}
containsStruct := Contains(list, target)
fmt.Println("contains struct:", containsStruct)

// Output:
// contains int32: true
// contains float64: true
// contains struct: true

详情请看这里:https://github.com/glassonion1/xgo/blob/main/contains.go

0赞 Zombo 5/29/2021 #14

如果你有切片,你可以使用包:bytebytes

package main
import "bytes"

func contains(b []byte, sub byte) bool {
   return bytes.Contains(b, []byte{sub})
}

func main() {
   b := contains([]byte{10, 11, 12, 13, 14}, 13)
   println(b)
}

或包装:suffixarray

package main
import "index/suffixarray"

func contains(b []byte, sub byte) bool {
   return suffixarray.New(b).Lookup([]byte{sub}, 1) != nil
}

func main() {
   b := contains([]byte{10, 11, 12, 13, 14}, 13)
   println(b)
}

如果你有切片,你可以使用包:intintsets

package main
import "golang.org/x/tools/container/intsets"

func main() {
   var s intsets.Sparse
   for n := 10; n < 20; n++ {
      s.Insert(n)
   }
   b := s.Has(16)
   println(b)
}
-2赞 natenho 11/20/2021 #15

有几个软件包可以提供帮助,但这个软件包似乎很有前途:

https://github.com/wesovilabs/koazee

var numbers = []int{1, 5, 4, 3, 2, 7, 1, 8, 2, 3}
contains, _ := stream.Contains(7)
fmt.Printf("stream.Contains(7): %v\n", contains)
55赞 Amar 1/21/2022 #16

在 Go 1.18+ 中,我们可以使用泛型。

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

评论

120赞 Abhijit Sarkar 2/12/2022
Go 是我最喜欢的语言,因为我喜欢从头开始创建其他语言提供的 OOTB 实用程序。
7赞 dimiguel 5/8/2022
@AbhijitSarkar 我意识到你很有面子,我也同意这应该在 stdlib 中,但泛型只是引入 Go 的。我更喜欢一种对它所引入的功能非常慎重并且保持相对简单的语言。我希望随着时间的流逝,这将被添加到Golang中。
10赞 Hem 9/21/2022
我宁愿花精力编写业务逻辑,也不愿编写实用程序。
5赞 Brandon 9/23/2022
似乎在 golang 中,他们正在用语言实现的简单性来换取使用它的人的额外复杂性。我假设每个 golang 开发人员只是在某个地方实现了所有基本功能,并在他们的项目之间复制和粘贴它。它基本上是 Javascript 的重头戏,只是由于进入门槛高,代码质量更高
234赞 Adolfo 2/19/2022 #17

从 Go 1.21 开始,您可以使用从前面提到的实验包升级的 stdlib 切片

import  "slices"

things := []string{"foo", "bar", "baz"}
slices.Contains(things, "foo") // true

原文答案:

从 Go 1.18 开始,您可以使用软件包——特别是通用函数:https://pkg.go.dev/golang.org/x/exp/slices#ContainsslicesContains

go get golang.org/x/exp/slices
import  "golang.org/x/exp/slices"
things := []string{"foo", "bar", "baz"}
slices.Contains(things, "foo") // true

请注意,由于它作为实验包在 stdlib 之外,因此它不受 Go 1 兼容性承诺™的约束,并且在正式添加到 stdlib 之前可能会更改。

评论

2赞 user5359531 5/25/2022
很遗憾,您不能将 Go 1.18 与旧版本的 macOS 一起使用
0赞 Kuldeep Yadav 1/23/2023
无法读取“github.com”的用户名:终端提示已禁用 Got above error duringgo get
0赞 Diansheng 5/25/2023 #18
func contains(slice []string, item string) bool {
    for _, s := range slice {
        if s == item {
            return true
        }
    }
    return false
}
2赞 AnhellO 7/19/2023 #19

作为对 @Adolfo 的回答(以及此处的其他答案)的补充,Contains 函数将不再是实验性的。从将于 2023 年 8 月发布的 GO v1.21 开始,上述切片包将包含在核心库中(除了其他一些有趣的包,如 和 ),从而可以对元素切片O(N) 时间复杂度)运行线性搜索。mapcmp

此外,您还将有一些其他有趣的变体来搜索切片中的一个或多个元素,例如新的 ContainsFunc,它报告 s 的至少一个元素是否满足 。您可以在下面的示例中检查这一点,该示例取自实际文档:ef(e)

package main

import (
    "fmt"
    "slices"
)

func main() {
    numbers := []int{0, 42, -10, 8}
    hasNegative := slices.ContainsFunc(numbers, func(n int) bool {
        return n < 0
    })
    fmt.Println("Has a negative:", hasNegative)
    hasOdd := slices.ContainsFunc(numbers, func(n int) bool {
        return n%2 != 0
    })
    fmt.Println("Has an odd number:", hasOdd)
}

此外,请记住,如果您正在使用排序切片,并且希望在搜索元素 (O(logN)) 时降低时间复杂度,您还可以使用 BinarySearchBinarySearchFunc 函数,它们也将随此新包一起提供。

最后,如果你想使搜索在时间上保持不变(O(1)),我会选择@tux21b在投票的答案中建议的方法,即使用地图。