6

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?

Thomas
  • 8,306
  • 8
  • 53
  • 92
  • 1
    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. – Jakumi Jul 22 '16 at 17:45
  • What is going into the Array? Are they any arbitrary values, or are they specific types that need to be compared? –  Jul 22 '16 at 19:27
  • @squint the arrays are "standard". I have an array []A and an array of []B (all structs, not interfaces) – Thomas Jul 25 '16 at 06:42

3 Answers3

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
}
nessuno
  • 26,493
  • 5
  • 83
  • 74
2

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.

  • 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) – Thomas Jul 22 '16 at 12:56
  • 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 readbale – Thomas Jul 25 '16 at 06:47
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.

mrkovec
  • 376
  • 4
  • 9
  • Thanks but this forces me to copy my array into []proxy. but maybe worth it I have to see – Thomas Jul 22 '16 at 12:57
  • I don't understand this solution. Why is `[]proxy` better than `[]interface{}`? – Paul Hankin Jul 23 '16 at 08:50
  • @PaulHankin Go doesn't compile when you pass in []A when you have an []inteface{} argument – Thomas Jul 25 '16 at 06:46
  • 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? – Paul Hankin Jul 25 '16 at 06:52
  • here is a [link](https://play.golang.org/p/l49fOsECDt) for clarification – mrkovec Jul 25 '16 at 09:04