【问题标题】:Contains method for a slice包含切片的方法
【发布时间】:2012-05-16 04:14:57
【问题描述】:

在 Go 中有什么类似于 slice.contains(object) 方法的东西,而无需搜索切片中的每个元素?

【问题讨论】:

标签: go slice


【解决方案1】:

Mostafa 已经pointed out 说这样的方法写起来很简单,mkb 给了你使用 sort 包中的二进制搜索的提示。但如果您要进行大量此类包含检查,您也可以考虑使用地图。

使用value, ok := yourmap[key] 成语检查是否存在特定的映射键很简单。由于您对该值不感兴趣,因此您还可以创建一个 map[string]struct{} 例如。在这里使用空的struct{} 的优点是它不需要任何额外的空间,并且 Go 的内部映射类型针对这种值进行了优化。因此,map[string] struct{} 是围棋世界中集合的流行选择。

【讨论】:

  • 另请注意,您必须编写struct{}{} 来获取空结构的值,以便在您想要添加元素时将其传递给您的地图。试试看,如果遇到任何问题,请随时提问。如果您更容易理解,您也可以使用 Mostafa 的解决方案(除非您有大量数据)。
  • 解决方案很简单,没错。但是将这些基本功能添加到运行时需要什么?我在 github 上的 Go repo 中没有发现这样的问题。这既可悲又奇怪。
  • map[string] boolmap[string] struct{} 相比如何。 map[string] struct{} 似乎是一个 hack,尤其是初始化一个空结构 struct {}{}
  • @IgorPetrov 同意,​​我很惊讶这样一个基本功能还没有在运行时中。
  • 荒谬的是你必须自己添加这个。
【解决方案2】:

不,这种方法不存在,但写起来很简单:

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

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

【讨论】:

  • 其实这不是微不足道的,因为你必须为你使用的每种类型编写一个,并且因为没有重载,你必须为每个函数命名不同,就像在 C 中一样。 append() 可以通用因为它有特殊的运行时支持。出于同样的原因,泛型包含会很有用,但实际上泛型解决方案只是语言中的泛型支持。
  • @Alex Lockwood 这真的适用于接口吗?
  • 琐碎 == 7 行代码,包括 1 个循环 1 个分支 if 语句和 1 个比较?我想我在这里遗漏了一些东西......
  • 但是为什么不将这些添加到 go core 本身呢?
  • @tothemario 欢迎来到 Go :)
【解决方案3】:

sort 包提供了如果您的切片已排序或您愿意对其进行排序的构建块。

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)),因此检查该字符串是否包含已排序的切片。

【讨论】:

  • 就时间而言,常规搜索是O(n),这个解决方案是O(n*log(n))
  • @plesiv 这是一个二分搜索,AFAICS。那不是 O(log n) 吗?
  • 是的,二分搜索和函数containsO(log(n)),但由于排序,整体方法是O(n*log(n))
【解决方案4】:

如果切片已排序,则在the sort package 中实现了二进制搜索。

【讨论】:

    【解决方案5】:

    而不是使用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

    【讨论】:

    • 在目前的形式中,这段代码没有任何好处,因为如果你只打算使用一次切片,那么从切片构造地图是没有意义的。 — 为了有用,这段代码应该提供一个函数sliceToMap 来完成所有的准备工作。之后,查询地图就变得简单而高效了。
    【解决方案6】:
    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
    }
    

    【讨论】:

      【解决方案7】:

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

      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

      【讨论】:

      • 当然你可以使用反射包,但仅仅因为你可以,并不意味着你应该。反射非常昂贵。
      • 在实际应用程序代码中,您不应该这样做。它是昂贵的。但是,对于单元测试来说,这并不重要,而且非常有用。
      【解决方案8】:

      如果使用地图基于键查找项目不可行,您可以考虑使用goderive 工具。 Goderive 生成​​特定于类型的 contains 方法实现,使您的代码既可读又高效。

      示例;

      type Foo struct {
          Field1 string
          Field2 int
      } 
      
      func Test(m Foo) bool {
           var allItems []Foo
           return deriveContainsFoo(allItems, m)
      }
      

      要生成 derivedContainsFoo 方法:

      • go get -u github.com/awalterschulze/goderive安装goderive
      • 在您的工作区文件夹中运行 goderive ./...

      该方法将为deriveContains生成:

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

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

      【讨论】:

        【解决方案9】:

        不确定此处是否需要泛型。你只需要一个你想要的行为的合同。如果您希望自己的对象在集合中表现自己,例如通过覆盖 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
        }
        

        【讨论】:

        • “只不过是你在其他语言中必须做的”并不是真的 - 例如在 C# 中,Contains() 是在 List&lt;T&gt; 上实现的,因此您只需实现 Equals() 即可。
        【解决方案10】:

        我认为map[x]boolmap[x]struct{} 更有用。

        为不存在的项目索引地图将返回false。所以你可以直接说m[X],而不是_, ok := m[X]

        这使得在表达式中嵌套包含测试变得容易。

        【讨论】:

          【解决方案11】:

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

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

          【讨论】:

            【解决方案12】:

            我用这些答案的解决方案创建了一个非常简单的基准。

            https://gist.github.com/NorbertFenk/7bed6760198800207e84f141c41d93c7

            这不是一个真正的基准,因为最初,我没有插入太多元素,但可以随意分叉和更改它。

            【讨论】:

            • 我想过,但由于我的机器不是那么强大,所以它没有那么有代表性。
            【解决方案13】:

            有几个packages that can help,但这个似乎很有希望:

            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)
            

            【讨论】:

              【解决方案14】:

              围棋风格:

              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"
              })
              

              【讨论】:

              • 这并没有回答问题,也没有提供额外的信息。
              【解决方案15】:

              如果你有一个byte 切片,你可以使用bytes 包:

              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)
              }
              

              如果你有一个int 切片,你可以使用intsets 包:

              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)
              }
              

              【讨论】:

                【解决方案16】:

                我使用反射包创建了以下包含函数。 该函数可用于各种类型,如 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

                【讨论】:

                  【解决方案17】:

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

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

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

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

                  【讨论】:

                  • 不适用于元素具有相似文本但不完全相同的情况exSlice := ["yes and no", "maybe", "maybe another"]
                  • 这是一种相当不错的方法,可以实现快速而简单的单线解决方案。您只需要一个明确的分隔符(可以是逗号)并做额外的工作将两个字符串括起来:","+strings.Join(exSlice,",")+","",maybe,"
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2016-10-27
                  • 2021-11-09
                  • 1970-01-01
                  • 1970-01-01
                  • 2017-07-08
                  • 1970-01-01
                  相关资源
                  最近更新 更多