在Go语言中,有一种通用的方法可以判断切片是否包含特定元素吗?

huangapple go评论90阅读模式
英文:

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,但显然我错了。每次遇到一个新的切片结构体,我都需要写这个函数吗?难道没有一种通用的方法来做这个吗?

英文:

I want to know is there a generic way to write code to judge whether a slice contains an element, I find it will frequently useful since there is a lot of logic to fist judge whether specific elem is already in a slice and then decide what to do next. But there seemed not a built-in method for that(For God's sake, why?)

I try to use interface{} to do that like:

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

I thought interface{} is sort of like Object of Java, but apparently, I was wrong. Should I write this every time meet with a new struct of slice? Isn't there a generic way to do this?

答案1

得分: 4

你可以使用reflect来实现,但它的速度比非泛型等效函数要慢得多

func Contains(slice, elem interface{}) bool {
    sv := reflect.ValueOf(slice)
    
    // 检查slice是否实际上是一个切片/数组。
    // 在这里你可能想返回一个错误
    if sv.Kind() != reflect.Slice && sv.Kind() != reflect.Array {
        return false
    }
    
    // 遍历切片
    for i := 0; i < sv.Len(); i++ {
        
        // 将elem与当前切片元素进行比较
        if elem == sv.Index(i).Interface() {
            return true
        }
    }
    
    // 未找到匹配项
    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"))
}

速度会慢大约50-60倍
与以下形式的非泛型函数进行基准测试:

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

我得到的结果是:

  • 泛型:N=100000,运行时间:73.023214ms 730.23214 ns/op
  • 非泛型:N=100000,运行时间:1.315262ms 13.15262 ns/op
英文:

You can do it with reflect, but it will be MUCH SLOWER than a non-generic equivalent function:

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 &amp;&amp; sv.Kind() != reflect.Array {
        return false
    }
    
    // iterate the slice
    for i := 0; i &lt; 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 {&quot;hello&quot;, &quot;world&quot;, &quot;foo&quot;, &quot;bar&quot;}
    
    fmt.Println(Contains(si, 3))
    fmt.Println(Contains(si, 100))
    fmt.Println(Contains(ss, &quot;hello&quot;))
    fmt.Println(Contains(ss, &quot;baz&quot;))
    
}

How much slower? about x50-x60 slower:
Benchmarking against a non generic function of the form:

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

I'm getting:

  • Generic: N=100000, running time: 73.023214ms 730.23214 ns/op
  • Non Generic: N=100000, running time: 1.315262ms 13.15262 ns/op

答案2

得分: 3

你可以使用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
}

Playground示例:http://play.golang.org/p/TQrmwIk6B4

另外,你还可以选择以下方式:

  • 定义一个接口并使你的切片实现它
  • 使用映射而不是切片
  • 只需编写一个简单的for循环

选择哪种方式取决于你要解决的问题。

英文:

You can make it using the reflect package like that:

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

Playground examples: http://play.golang.org/p/TQrmwIk6B4

Alternatively, you can:

  • define an interface and make your slices implement it
  • use maps instead of slices
  • just write a simple for loop

What way to choose depends on the problem you are solving.

答案3

得分: 0

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

package main

import "fmt"

type PublicClassObjectBuilderFactoryStructure struct {
    Tee string
    Hee string
}

func main() {
    // 空结构体占用零字节。
    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{}{}

    // 下划线忽略了值,该值是一个空结构体。
    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函数。这里有一个示例。

英文:

I'm not sure what your specific context is, but you'll probably want to use a map to check if something already exists.

package main

import &quot;fmt&quot;

type PublicClassObjectBuilderFactoryStructure struct {
    Tee string
    Hee string
}

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

    one := PublicClassObjectBuilderFactoryStructure{Tee: &quot;hi&quot;, Hee: &quot;hey&quot;}
    two := PublicClassObjectBuilderFactoryStructure{Tee: &quot;hola&quot;, Hee: &quot;oye&quot;}

    three := PublicClassObjectBuilderFactoryStructure{Tee: &quot;hi&quot;, Hee: &quot;again&quot;}

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

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

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

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

Another advantage of using maps instead of a slice is that there is a built-in delete function for maps. https://play.golang.org/p/dmSyyryyS8

答案4

得分: 0

如果你想要一个不同的解决方案,你可以尝试使用像Gen这样的代码生成工具。Gen为你想要在切片中保存的每个具体类编写源代码,因此它支持类型安全的切片,可以让你搜索第一个匹配的元素

(Gen还提供了其他几种集合类型,并允许你自己编写。)

英文:

If you want a rather different solution, you might try the code-generator approach offered by tools such as 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 also offers a few other kinds of collection and allows you to write your own.)

huangapple
  • 本文由 发表于 2015年3月3日 17:40:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/28828440.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定