In Go, how can I make a generic function with slices?
Asked Answered
I

4

7

Let's say I want to write a function that finds a value in a slice

I intuitively want to write:

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

however I don't manage to do this with Go. I could have an interface with

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

and this would work but in my real code things are more complicated (I need to do slices[from:end] etc), append, ... etc and if I redefine all this in an interface I end up having a lot of code. Is there a better way?

Iota answered 22/7, 2016 at 11:57 Comment(3)
The answer would be generics, but go doesn't do generics. So effectively you use reflection, write n find_[type] functions or walk around with interfaces everywhere. It's just not enjoyable in go so far.Carbonaceous
What is going into the Array? Are they any arbitrary values, or are they specific types that need to be compared?Seisin
@squint the arrays are "standard". I have an array []A and an array of []B (all structs, not interfaces)Iota
S
4

Seeing as all of the answers here were written before go added generics, I'll add how you would implement this in go1.18+

Note, however, that as of go1.21, there is a slices package that contains many helpful, generic slice functions.

This is the implementation of IndexFunc straight from the standard library

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

Notice how the function uses the type parameters [S ~[]E, E any] instead of just S []any. The difference is due do how slices are typed in go. Just like how you can't assert a slice of one type to a slice of another even if it's possible to assert the elements (ex. []int{}.([]any) is invalid even though int satisfies any), you can't use an []int as a parameter of type S if S is constrained to []any.

Instead, it uses S ~[]E to constrain S to be a slice with elements of type E, where E can be any type. Additionally, this allows functions to use the element type separately, which you can see with the second argument: f func(E) bool.

Servais answered 17/11, 2023 at 17:6 Comment(0)
W
6

You can use reflection. I wrote this function for a project, feel free to use it:

// 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
}
Whiteside answered 22/7, 2016 at 12:16 Comment(1)
maybe show how to use it? I guess afterwards you have to cast it to the desired type?Callboy
S
4

Seeing as all of the answers here were written before go added generics, I'll add how you would implement this in go1.18+

Note, however, that as of go1.21, there is a slices package that contains many helpful, generic slice functions.

This is the implementation of IndexFunc straight from the standard library

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

Notice how the function uses the type parameters [S ~[]E, E any] instead of just S []any. The difference is due do how slices are typed in go. Just like how you can't assert a slice of one type to a slice of another even if it's possible to assert the elements (ex. []int{}.([]any) is invalid even though int satisfies any), you can't use an []int as a parameter of type S if S is constrained to []any.

Instead, it uses S ~[]E to constrain S to be a slice with elements of type E, where E can be any type. Additionally, this allows functions to use the element type separately, which you can see with the second argument: f func(E) bool.

Servais answered 17/11, 2023 at 17:6 Comment(0)
M
3

if you have predefined type like []int or []string and do not want to convert to []interface{} see this working sample code (without using 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
}

or you may use reflect,like this working sample code:

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
}

output:

5
1

I hope this helps.

Mail answered 22/7, 2016 at 12:11 Comment(2)
Thanks but I already have 2 arrays: a := A[]{} and b := B[]{} how does what you suggest work in that case? find(a, fa) and find(b, fb) cannot be called. (I want just 1 find function to avoid to duplicate it)Iota
thanks, but that's now the answer above :) but thanks for the hard work, I +1. If you want you can keep just the last part in your answer, it is the best and makes things more readbaleIota
V
0

I think, if you want to have slice of arbitrary values and use that sort of find function and have the possibility of standard [] reslicing, maybe the best way is to encapsulate your interface{} with another struct

type proxy struct {
    val interface{}
}

and use

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

and have the f function deal with interface{} comparison / type casting.

Valente answered 22/7, 2016 at 12:39 Comment(5)
Thanks but this forces me to copy my array into []proxy. but maybe worth it I have to seeIota
I don't understand this solution. Why is []proxy better than []interface{}?Chintzy
@PaulHankin Go doesn't compile when you pass in []A when you have an []inteface{} argumentIota
Nor does is compile if you pass in []A when you have a []proxy argument. In both cases one needs to wrap the underlying data in a common type -- this answer suggests proxy, but I don't see why that's better than just using interface{}. Am I missing something?Chintzy
here is a link for clarificationValente

© 2022 - 2024 — McMap. All rights reserved.