137

How does one determine the position of an element present in slice?

I need something like the following:

type intSlice []int

func (slice intSlice) pos(value int) int {
    for p, v := range slice {
        if (v == value) {
            return p
        }
    }
    return -1
}
seh
  • 14,999
  • 2
  • 48
  • 58
OCyril
  • 2,875
  • 4
  • 20
  • 11
  • 2
    so, what's the question? does the code not work? – newacct Nov 29 '11 at 20:14
  • 23
    I was asking why should go coders write such common functions by themselves? I mean if I want an another function for float values I'll be forced to copy/paste this function. This looked weird for me. But Krzysztof Kowalczyk has already answered, that it is because golang doesn't has generics. – OCyril Nov 30 '11 at 16:24
  • Is your slice sorted? – dolmen Oct 20 '15 at 13:43
  • 2
    Try this source: https://gobyexample.com/collection-functions – Victor Sep 13 '16 at 20:19

11 Answers11

82

Sorry, there's no generic library function to do this. Go doesn't have a straight forward way of writing a function that can operate on any slice.

Your function works, although it would be a little better if you wrote it using range.

If you happen to have a byte slice, there is bytes.IndexByte.

Evan Shaw
  • 23,839
  • 7
  • 70
  • 61
  • 4
    I agree with Evan. Additional comment: it's more idiomatic to return just an int where -1 indicates "not found" (like bytes.IndexByte) – Krzysztof Kowalczyk Nov 29 '11 at 08:22
  • 4
    Thanks. But I'm overwhelmed a little :) I heard many times from people using golang, that it is designed very well to increase programmer's productivity. And go programs looks as nice as python ones :) So why there is no a common way to do such a common task? I mean, if you want to check if container has an element you can just `if element in collection: do_something()` – OCyril Nov 29 '11 at 18:26
  • 13
    The technical answer is: because Go doesn't have generics. If it did (and maybe at some point in the future it will have them) you could have written a generic IndexInSlice that works on any type that implements ==. Putting my Go advocate hat: it's about an average experience. You can't expect a single language to beat every other language in every aspect. Go *is* much more productive than C, C++, maybe even Java or C# and close to python. It's the combination of programmer productivity and native code generation (i.e. speed) that makes it attractive. – Krzysztof Kowalczyk Nov 29 '11 at 22:26
  • 1
    Another option is to use higher order functions and closures for creation of generic functions. I added answer with an example. – Hodza Aug 13 '13 at 08:17
  • @KrzysztofKowalczyk How is this more idiomatic? Isn't the go way to return the zero value and false in these scenarios? 'func (slice intSlice) pos(value int) (int, bool)'. Incidentally that's how map[T]T's builtin existence check works. – tbone Sep 11 '13 at 08:02
  • @tbone I think that returning -1 when searching in array it's ok. But for maps it isn't because a negative integer (i.e. -1) can be a key too, so a false must be returned when the key was not found. – eAbi Feb 07 '14 at 23:23
  • 1
    @OCyril You have to write your own generics and build your own library or do the research and find something that fits your needs. At the lowest level the 'find value=x in array' function (PHP, Python or whatever) will look somewhat like the version given in the OP's question - at a low level. Loops are central to this kind of programming, even if they are hidden. Go does not hide this stuff. – Ian Lewis May 21 '14 at 13:37
71

You can create generic function in idiomatic go way:

func SliceIndex(limit int, predicate func(i int) bool) int {
    for i := 0; i < limit; i++ {
        if predicate(i) {
            return i
        }
    }
    return -1
}

And usage:

xs := []int{2, 4, 6, 8}
ys := []string{"C", "B", "K", "A"}
fmt.Println(
    SliceIndex(len(xs), func(i int) bool { return xs[i] == 5 }),
    SliceIndex(len(xs), func(i int) bool { return xs[i] == 6 }),
    SliceIndex(len(ys), func(i int) bool { return ys[i] == "Z" }),
    SliceIndex(len(ys), func(i int) bool { return ys[i] == "A" }))
Hodza
  • 3,118
  • 26
  • 20
  • 1
    Love this, but I do find it hard to come to think of it this way – Decebal Sep 26 '15 at 12:06
  • 2
    I don't think returning `-1` for an error is idiomatic, it should use multiple return instead. (I'm new to golang but that's what I've read) – Tim Abell Oct 24 '15 at 19:10
  • 9
    Buildtin index funcs in go always return -1. This is expected idiomatic behaviour. Missing element is not an error. – Hodza Oct 29 '15 at 12:02
  • 1
    This is fantastic! Very useful :) Thanks! – Gaurav Ojha Mar 25 '17 at 07:03
  • you will get that very same implementation (which is the most appropriate imho) in that package https://pkg.go.dev/github.com/thoas/go-funk with some additinnal type safe versions. –  Feb 19 '22 at 12:20
29

You could write a function;

func indexOf(element string, data []string) (int) {
   for k, v := range data {
       if element == v {
           return k
       }
   }
   return -1    //not found.
}

This returns the index of a character/string if it matches the element. If its not found, returns a -1.

Community
  • 1
  • 1
PodTech.io
  • 4,874
  • 41
  • 24
8

Go supports generics as of version 1.18, which allows you to create a function like yours as follows:

func IndexOf[T comparable](collection []T, el T) int {
    for i, x := range collection {
        if x == el {
            return i
        }
    }
    return -1
}

If you want to be able to call IndexOf on your collection you can alternatively use @mh-cbon's technique from the comments.

Levi Botelho
  • 24,626
  • 5
  • 61
  • 96
8

There is no library function for that. You have to code by your own.

alessandro
  • 1,681
  • 10
  • 33
  • 54
6

Since Go 1.18 you can also use the experimental generic slices package from https://pkg.go.dev/golang.org/x/exp/slices like this:

package main

import "golang.org/x/exp/slices"

func main() {
    s := []int{1,2,3,4,5}
    wanted := 3
    idx := slices.Index(s, wanted)
    fmt.Printf("the index of %v is %v", wanted, idx)
}

It will return -1, if wanted is not in the slice. Test it at the playground.

This is my preferred way, since this might become part of the standard library someday.

dmorawetz
  • 497
  • 4
  • 12
5

You can just iterate of the slice and check if an element matches with your element of choice.

func index(slice []string, item string) int {
    for i := range slice {
        if slice[i] == item {
            return i
        }
    }
    return -1
}
0xInfection
  • 2,676
  • 1
  • 19
  • 34
user60679
  • 709
  • 14
  • 28
2

Use slices.Index in Go 1.21 and later.

haystack := []string{"foo", "bar", "quux"}
fmt.Println(slices.Index(haystack, "bar")) // prints 1
fmt.Println(slices.Index(haystack, "rsc")) // prints -1
thwd
  • 126
  • 3
1

Another option is to sort the slice using the sort package, then search for the thing you are looking for:

package main

import (
    "sort"
    "log"
    )

var ints = [...]int{74, 59, 238, -784, 9845, 959, 905, 0, 0, 42, 7586, -5467984, 7586}

func main() {
        data := ints
        a := sort.IntSlice(data[0:])
        sort.Sort(a)
        pos := sort.SearchInts(a, -784)
        log.Println("Sorted: ", a)
        log.Println("Found at index ", pos)
}

prints

2009/11/10 23:00:00 Sorted:  [-5467984 -784 0 0 42 59 74 238 905 959 7586 7586 9845]
2009/11/10 23:00:00 Found at index  1

This works for the basic types and you can always implement the sort interface for your own type if you need to work on a slice of other things. See http://golang.org/pkg/sort

Depends on what you are doing though.

robothor
  • 600
  • 4
  • 6
  • Ok, thanks. But I guess it looks weird to use this if I only want to check if slice contains given element :) – OCyril Nov 29 '11 at 18:18
  • 3
    this does not find the original index; rather it loses all the indexes by re-ordering them – newacct Nov 29 '11 at 20:16
  • Sure, that's why it depends on the usage. If it is just a check, then sure, just use `for p, v := range ...` and `if`. Just wanted to point out the option. – robothor Nov 30 '11 at 08:39
0

I had the same issue few months ago and I solved in two ways:

First method:

func Find(slice interface{}, f func(value interface{}) bool) int {
    s := reflect.ValueOf(slice)
    if s.Kind() == reflect.Slice {
        for index := 0; index < s.Len(); index++ {
            if f(s.Index(index).Interface()) {
                return index
            }
        }
    }
    return -1
}

Use example:

type UserInfo struct {
    UserId          int
}

func main() {
    var (
        destinationList []UserInfo
        userId      int = 123
    )
    
    destinationList = append(destinationList, UserInfo { 
        UserId          : 23,
    }) 
    destinationList = append(destinationList, UserInfo { 
        UserId          : 12,
    }) 
    
    idx := Find(destinationList, func(value interface{}) bool {
        return value.(UserInfo).UserId == userId
    })
    
    if idx < 0 {
        fmt.Println("not found")
    } else {
        fmt.Println(idx)    
    }
}

Second method with less computational cost:

func Search(length int, f func(index int) bool) int {
    for index := 0; index < length; index++ {
        if f(index) {
            return index
        }
    }
    return -1
}

Use example:

type UserInfo struct {
    UserId          int
}

func main() {
    var (
        destinationList []UserInfo
        userId      int = 123
    )
    
    destinationList = append(destinationList, UserInfo { 
        UserId          : 23,
    }) 
    destinationList = append(destinationList, UserInfo { 
        UserId          : 123,
    }) 
    
    idx := Search(len(destinationList), func(index int) bool {
        return destinationList[index].UserId == userId
    })
    
    if  idx < 0 {
        fmt.Println("not found")
    } else {
        fmt.Println(idx)    
    }
}
omotto
  • 1,721
  • 19
  • 20
0

Another option if your slice is sorted is to use SearchInts(a []int, x int) int which returns the element index if it's found or the index the element should be inserted at in case it is not present.

s := []int{3,2,1}
sort.Ints(s)
fmt.Println(sort.SearchInts(s, 1)) // 0
fmt.Println(sort.SearchInts(s, 4)) // 3

https://play.golang.org/p/OZhX_ymXstF

Alan Sereb
  • 2,358
  • 2
  • 17
  • 31