有没有一种方法可以编写通用代码来查明切片是否包含 Go 中的特定元素?

Is there a way to write generic code to find out whether a slice contains specific element in Go?

我想知道有没有一种通用的方法来编写代码来判断一个切片是否包含一个元素,我发现它经常有用,因为有很多逻辑来判断特定的元素是否已经在一个切片中然后决定下一步做什么。但是似乎没有内置的方法(看在上帝的份上,为什么?)

我尝试使用 interface{} 来做到这一点:

func sliceContains(slice []interface{}, elem interface{}) bool {
    for _, item := range slice {
       if item == elem {
          return true
       }
    }
    return false
}

我认为 interface{} 有点像 Java 的 Object,但显然我错了。我是否应该在每次遇到新的 slice 结构时都写这个?没有通用的方法来做到这一点吗?

我不确定你的具体上下文是什么,但你可能想要使用 map 来检查是否已经存在。

package main

import "fmt"

type PublicClassObjectBuilderFactoryStructure struct {
    Tee string
    Hee string
}

func main() {
    // Empty structs occupy zero bytes.
    mymap := map[interface{}]struct{}{}

    one := PublicClassObjectBuilderFactoryStructure{Tee: "hi", Hee: "hey"}
    two := PublicClassObjectBuilderFactoryStructure{Tee: "hola", Hee: "oye"}

    three := PublicClassObjectBuilderFactoryStructure{Tee: "hi", Hee: "again"}

    mymap[one] = struct{}{}
    mymap[two] = struct{}{}

    // The underscore is ignoring the value, which is an empty struct.
    if _, exists := mymap[one]; exists {
        fmt.Println("one exists")
    }

    if _, exists := mymap[two]; exists {
        fmt.Println("two exists")
    }

    if _, exists := mymap[three]; exists {
        fmt.Println("three exists")
    }
}

使用地图而不是切片的另一个优点是地图有一个内置的 delete 函数。 https://play.golang.org/p/dmSyyryyS8

您可以像这样使用 reflect 包来制作它:

func In(s, e interface{}) bool {
    slice, elem := reflect.ValueOf(s), reflect.ValueOf(e)
    for i := 0; i < slice.Len(); i++ {
        if reflect.DeepEqual(slice.Index(i).Interface(), elem.Interface()) {
            return true
        }
    }
    return false
}

游乐场示例:http://play.golang.org/p/TQrmwIk6B4

或者,您可以:

  • 定义接口并让您的切片实现它
  • 使用地图而不是切片
  • 写一个简单的for循环就可以了

选择什么方式取决于你要解决的问题。

您可以使用 reflect 来完成,但它 比非通用等效函数 慢得多:

func Contains(slice, elem interface{}) bool {

    sv := reflect.ValueOf(slice)

    // Check that slice is actually a slice/array. 
    // you might want to return an error here
    if sv.Kind() != reflect.Slice && sv.Kind() != reflect.Array {
        return false
    }

    // iterate the slice
    for i := 0; i < sv.Len(); i++ {

        // compare elem to the current slice element
        if elem == sv.Index(i).Interface() {
            return true
        }
    }

    // nothing found
    return false


}

func main(){
    si := []int {3, 4, 5, 10, 11}
    ss := []string {"hello", "world", "foo", "bar"}

    fmt.Println(Contains(si, 3))
    fmt.Println(Contains(si, 100))
    fmt.Println(Contains(ss, "hello"))
    fmt.Println(Contains(ss, "baz"))

}

慢了多少? 大约 x50-x60 速度较慢: 针对以下形式的非通用函数进行基准测试:

func ContainsNonGeneic(slice []int, elem int) bool {
    for _, i := range slice {
        if i == elem {
            return true
        }
    }
    return false
}

我得到:

  • 通用:N=100000, running time: 73.023214ms 730.23214 ns/op
  • 非通用:N=100000, running time: 1.315262ms 13.15262 ns/op

如果您想要一个完全不同的解决方案,您可以尝试 Gen. Gen writes source code for each concrete class you want to hold in a slice, so it supports type-safe slices that let you search for the first match of an element.

等工具提供的代码生成器方法

(Gen 还提供了一些其他类型的集合,并允许您编写自己的集合。)