在Go中,我如何使用切片创建一个泛型函数?

hyrbngr7  于 5个月前  发布在  Go
关注(0)|答案(4)|浏览(89)

假设我想写一个函数,在切片中查找一个值
我本能地想写:

func find(s []interface{}, f func(interface{})bool) int {
    for i, item := range s {
        if f(item) {
            return i
        }
    }
    return -1
}

字符串
但是我不能用Go来做这个。我可以有一个接口,

Len() int
Value(int) interface{}
...


这是可行的,但在我的真实的代码中,事情要复杂得多(我需要做切片[from:end]等),追加,.等,如果我在一个接口中重新定义所有这些,我最终会有很多代码。有更好的方法吗?

raogr8fs

raogr8fs1#

你可以使用反射。我为一个项目写了这个函数,请随意使用它:

// InSlice returns true if value is in slice
func InSlice(value, slice interface{}) bool {
    switch reflect.TypeOf(slice).Kind() {
    case reflect.Slice, reflect.Ptr:
        values := reflect.Indirect(reflect.ValueOf(slice))
        if values.Len() == 0 {
            return false
        }

        val := reflect.Indirect(reflect.ValueOf(value))

        if val.Kind() != values.Index(0).Kind() {
            return false
        }

        for i := 0; i < values.Len(); i++ {
            if reflect.DeepEqual(values.Index(i).Interface(), val.Interface()) {
                return true
            }
        }
    }
    return false
}

字符串

hkmswyz6

hkmswyz62#

鉴于这里的答案都不是在go添加泛型之前写的,我将添加如何在go1.18+中实现它
但是请注意,从go1.21开始,有一个slices包包含许多有用的通用切片函数。
这是直接来自标准库的IndexFunc的实现

// IndexFunc returns the first index i satisfying f(s[i]),
// or -1 if none do.
func IndexFunc[S ~[]E, E any](s S, f func(E) bool) int {
    for i := range s {
        if f(s[i]) {
            return i
        }
    }
    return -1
}

字符串
注意函数是如何使用类型参数[S ~[]E, E any]而不仅仅是S []any的。区别在于go中切片的类型。就像你可以将一种类型的切片Assert为另一种类型的切片,即使可以Assert元素(例如,即使int满足any[]int{}.([]any)也是无效的),如果S被限制为[]any,则可以使用[]int作为S类型的参数。
相反,它使用S ~[]ES约束为具有E类型元素的切片,其中E可以是任何类型。此外,这允许函数单独使用元素类型,您可以通过第二个参数看到:f func(E) bool

iyzzxitl

iyzzxitl3#

如果您有预定义类型,如[]int[]string,但不想转换为[]interface{},请参阅以下工作示例代码(不使用reflect):

package main

import "fmt"

func find(s []int, f func(int) bool) int {
    for i, item := range s {
        if f(item) {
            return i
        }
    }
    return -1
}
func findString(s []string, f func(string) bool) int {
    for i, item := range s {
        if f(item) {
            return i
        }
    }
    return -1
}

func main() {
    s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    fmt.Println(find(s, func(a int) bool { return a == 5 })) //5

    strs := []string{"A", "B", "C"}
    fmt.Println(findString(strs, func(a string) bool { return a == "B" })) //1
}

字符串
或者你可以使用reflect,像这样的工作示例代码:

package main

import "fmt"
import "reflect"

func find(slice interface{}, f func(interface{}) bool) int {
    switch reflect.TypeOf(slice).Kind() {
    case reflect.Slice:
        values := reflect.Indirect(reflect.ValueOf(slice))
        for i := 0; i < values.Len(); i++ {
            if f(values.Index(i).Interface()) {
                return i
            }
        }
    }
    return -1
}

func main() {
    a := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    fmt.Println(find(a, func(i interface{}) bool { return i == 5 })) //5

    b := []string{"A", "B", "C"}
    fmt.Println(find(b, func(i interface{}) bool { return i == "B" })) //1
}


产出:

5
1


我希望这能帮上忙。

kdfy810k

kdfy810k4#

我认为,如果你想拥有任意值的切片,使用那种find函数,并有可能进行标准的[]重新切片,也许最好的方法是用另一个结构体封装你的interface{}

type proxy struct {
    val interface{}
}

字符串
和使用

func find(s []proxy , f func(proxy)bool) int {}


并让f函数处理interface{}比较/类型转换。

相关问题