469

How can I check if x is in an array without iterating over the entire array, using Go? Does the language have a construct for this?

Like in Python:

if "x" in array: 
  # do something
Amal Thundiyil
  • 307
  • 1
  • 4
  • 9
  • 2
    Might be a dupe of http://stackoverflow.com/q/8307478/180100 –  Mar 10 '13 at 15:22
  • 11
    AFAIK, There is not shorthand for that in go. Internally, python also iterates over the array, there is no going around that. – Daniel Shaulov Mar 10 '13 at 15:24
  • 9
    BTW, an important note to this is that there *is no way* to do this (as you ask for) "**[w]ithout** iterating over the entire array". Making such loops explicit (or behind a function such as `strings.Index`) helps make it more obvious what the code is doing. I get the impression that perhaps you think Python's `in array:` is doing something fast/magic. AFAIK it isn't. Making the loop explicit helps make the writer (and all readers) aware and consider other implementations (e.g. a map). – Dave C Apr 16 '15 at 17:32
  • 7
    However, if "x" in set is indeed very fast. – Roberto Alsina Jun 16 '17 at 13:47
  • 16
    I don't think we're asking for a programatically fast approach, we're just asking for a concise one (and still haven't got...) – Migwell Jan 29 '19 at 05:05

9 Answers9

539

There is no built-in operator to do it in Go. You need to iterate over the array. You can write your own function to do it, like this:

func stringInSlice(a string, list []string) bool {
    for _, b := range list {
        if b == a {
            return true
        }
    }
    return false
}

Or in Go 1.18 or newer, you can use slices.Contains (from golang.org/x/exp/slices).

If you want to be able to check for membership without iterating over the whole list, you need to use a map instead of an array or slice, like this:

visitedURL := map[string]bool {
    "http://www.google.com": true,
    "https://paypal.com": true,
}
if visitedURL[thisSite] {
    fmt.Println("Already been here.")
}
Marko
  • 733
  • 8
  • 21
andybalholm
  • 15,395
  • 3
  • 37
  • 41
  • 6
    is there any way to do this without specifying type? let's say if i want just a general needleInHaystack(needle, haystack) function without separate methods for every single type – Allen May 13 '15 at 04:33
  • 38
    It could be done with the reflect package, but it would be fairly inefficient (probably about as slow as if you wrote it out in a dynamic language like Python). Other than that, no. That's what people mean when they say that Go doesn't have generics. – andybalholm May 13 '15 at 15:44
  • 5
    Anyone stumbled upon this answer must note that you CANNOT sort maps. Big downside to using go maps. – RisingSun Nov 12 '15 at 21:10
  • 4
    you can't sort maps (objects) in Javascript also. It's a v8 *bug* that objects return values in alphabetically sorted order. – kumarharsh Feb 24 '16 at 13:25
  • 10
    maps aren't sorted in most languages -- that's par for the course for a map data structure (hashmap). – Hejazzman Mar 29 '18 at 09:30
  • 2
    @Hejazzman many other mainstream languages have sorted maps in addition to hash maps, e.g. `map` in C++ and `SortedMap` in Java – scottysseus Sep 18 '19 at 14:48
  • 2
    I find new things I dislike about Go almost every day. Feels like we took a step back – alex Nov 13 '21 at 05:52
  • @andybalholm Go now has generics, how could this answer be updated/can this now be optimized? – Benjamin Miller Mar 29 '22 at 17:04
  • You could write an `inSlice` function that would work for all comparable types. It would be as fast as the `stringInSlice` function, but no faster. – andybalholm Mar 30 '22 at 16:49
  • Come to think of it, there's a function for that in golang.org/x/exp/slices. I've edited my answer to include it. – andybalholm Mar 30 '22 at 17:03
161

Another solution if the list contains static values.

eg: checking for a valid value from a list of valid values:

func IsValidCategory(category string) bool {
    switch category {
    case
        "auto",
        "news",
        "sport",
        "music":
        return true
    }
    return false
}
sebest
  • 2,119
  • 1
  • 14
  • 5
57

This is quote from the book "Programming in Go: Creating Applications for the 21st Century":

Using a simple linear search like this is the only option for unsorted data and is fine for small slices (up to hundreds of items). But for larger slices—especially if we are performing searches repeatedly—the linear search is very inefficient, on average requiring half the items to be compared each time.

Go provides a sort.Search() method which uses the binary search algorithm: This requires the comparison of only log2(n) items (where n is the number of items) each time. To put this in perspective, a linear search of 1000000 items requires 500000 comparisons on average, with a worst case of 1000000 comparisons; a binary search needs at most 20 comparisons, even in the worst case.

files := []string{"Test.conf", "util.go", "Makefile", "misc.go", "main.go"}
target := "Makefile"
sort.Strings(files)
i := sort.Search(len(files),
    func(i int) bool { return files[i] >= target })
if i < len(files) && files[i] == target {
    fmt.Printf("found \"%s\" at files[%d]\n", files[i], i)
}

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

AlexTT
  • 903
  • 1
  • 9
  • 12
  • 13
    This only makes sense if you do repeated searches. Otherwise you've got complexity n*log(n)*log(n) for the sort and binary search, versus just n for the linear search. – christian Oct 17 '17 at 16:17
  • 12
    It's actually just `n*log(n) + log(n)`, as it's two consequent independent operations – pomo_mondreganto Jun 16 '20 at 17:47
46

Just had a similar question and decided to try out some of the suggestions in this thread.

I've benchmarked best and worst-case scenarios of 3 types of lookup:

  • using a map
  • using a list
  • using a switch statement

Here's the function code:

func belongsToMap(lookup string) bool {
list := map[string]bool{
    "900898296857": true,
    "900898302052": true,
    "900898296492": true,
    "900898296850": true,
    "900898296703": true,
    "900898296633": true,
    "900898296613": true,
    "900898296615": true,
    "900898296620": true,
    "900898296636": true,
}
if _, ok := list[lookup]; ok {
    return true
} else {
    return false
}
}


func belongsToList(lookup string) bool {
list := []string{
    "900898296857",
    "900898302052",
    "900898296492",
    "900898296850",
    "900898296703",
    "900898296633",
    "900898296613",
    "900898296615",
    "900898296620",
    "900898296636",
}
for _, val := range list {
    if val == lookup {
        return true
    }
}
return false
}

func belongsToSwitch(lookup string) bool {
switch lookup {
case
    "900898296857",
    "900898302052",
    "900898296492",
    "900898296850",
    "900898296703",
    "900898296633",
    "900898296613",
    "900898296615",
    "900898296620",
    "900898296636":
    return true
}
return false
}

Best-case scenarios pick the first item in lists, worst-case ones use nonexistent value.

Here are the results:

BenchmarkBelongsToMapWorstCase-4         2000000           787 ns/op
BenchmarkBelongsToSwitchWorstCase-4     2000000000           0.35 ns/op
BenchmarkBelongsToListWorstCase-4       100000000           14.7 ns/op
BenchmarkBelongsToMapBestCase-4          2000000           683 ns/op
BenchmarkBelongsToSwitchBestCase-4      100000000           10.6 ns/op
BenchmarkBelongsToListBestCase-4        100000000           10.4 ns/op

Switch wins all the way, worst case is surpassingly quicker than best case.

Maps are the worst and list is closer to switch.

So the moral is: If you have a static, reasonably small list, switch statement is the way to go.

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
Igor
  • 469
  • 4
  • 2
  • I don't know if Go optimizes this case, but does it make a difference if you move the initialization of the list/map outside the test function? – w00t Apr 09 '19 at 13:51
  • I'm curious to see how the comparison would play out with a sorted list and whether the sort would be worth it – Michael Draper Apr 17 '19 at 23:48
  • What about `:` instead of `,` in the switch statement? Does it make it faster? – Thomas Sauvajon Feb 10 '20 at 03:32
  • I tried using multiple `case` statements instead of a single case. Results are sensibly the same with both functions. – Thomas Sauvajon Feb 10 '20 at 03:39
  • 4
    Nothing like providing a solution and using a benchmark to compare it to the most 'obvious' other solutions — backing up your suggestion with _data_, not speculation! Thank you — I'll stick to switches, since I have a fixed, static amount of checks... – Gwyneth Llewelyn Jul 22 '20 at 14:42
  • Something I'm not getting... how can it be faster to "NOT" be in there? is this some kind of hashmap-ing optimization done by Go? Is it because they're all "strings" that starts with the same stuff (900898[23]) and it's checked rune by rune ? – Eric L. Dec 30 '21 at 02:02
  • 2
    This benchmark is quite badly flawed. You're initialising the structure to be searched within the same method that you're benchmarking. I re-implemented this & put the initialisation of those lists & maps into the `func Bench...` methods. When not including the map initialisation or the slice initialisation, searching the map is more than twice as fast by the time you have 10 keys in the structure to be searched. This difference only gets worse for the slice as the number of entries increases. – Duncan Gener8 Mar 28 '22 at 13:48
  • @DuncanGener8, can you share your test method and results in a separate answer? – kivagant Mar 08 '23 at 17:43
29

The above example using sort is close, but in the case of strings simply use SearchString:

files := []string{"Test.conf", "util.go", "Makefile", "misc.go", "main.go"}
target := "Makefile"
sort.Strings(files)
i := sort.SearchStrings(files, target)
if i < len(files) && files[i] == target {
    fmt.Printf("found \"%s\" at files[%d]\n", files[i], i)
}

https://golang.org/pkg/sort/#SearchStrings

shadyyx
  • 15,825
  • 6
  • 60
  • 95
Robert Weber
  • 299
  • 3
  • 3
14

This is as close as I can get to the natural feel of Python's "in" operator. You have to define your own type. Then you can extend the functionality of that type by adding a method like "has" which behaves like you'd hope.

package main

import "fmt"

type StrSlice []string

func (list StrSlice) Has(a string) bool {
    for _, b := range list {
        if b == a {
            return true
        }
    }
    return false
}

func main() {
    var testList = StrSlice{"The", "big", "dog", "has", "fleas"}

    if testList.Has("dog") {
        fmt.Println("Yay!")
    }
}

I have a utility library where I define a few common things like this for several types of slices, like those containing integers or my own other structs.

Yes, it runs in linear time, but that's not the point. The point is to ask and learn what common language constructs Go has and doesn't have. It's a good exercise. Whether this answer is silly or useful is up to the reader.

IcarusNM
  • 951
  • 7
  • 16
8

Another option is using a map as a set. You use just the keys and having the value be something like a boolean that's always true. Then you can easily check if the map contains the key or not. This is useful if you need the behavior of a set, where if you add a value multiple times it's only in the set once.

Here's a simple example where I add random numbers as keys to a map. If the same number is generated more than once it doesn't matter, it will only appear in the final map once. Then I use a simple if check to see if a key is in the map or not.

package main

import (
    "fmt"
    "math/rand"
)

func main() {
    var MAX int = 10

    m := make(map[int]bool)

    for i := 0; i <= MAX; i++ {
        m[rand.Intn(MAX)] = true
    }

    for i := 0; i <= MAX; i++ {
        if _, ok := m[i]; ok {
            fmt.Printf("%v is in map\n", i)
        } else {
            fmt.Printf("%v is not in map\n", i)
        }
    }
}

Here it is on the go playground

nobled
  • 2,601
  • 2
  • 14
  • 15
7

In Go 1.18+1.21+, you can now declare generic Contains function or import in the experimental slice package the built-in slice package. It works for any comparable type

func Contains[T comparable](arr []T, x T) bool {
    for _, v := range arr {
        if v == x {
            return true
        }
    }
    return false
}

and use it like this:

if Contains(arr, "x") {
    // do something
}

or by importing

// func[S ~[]E, E comparable](s S, v E) bool
if slices.Contains(arr, "x") {
    // do something
}

which I found here

go je jo
  • 311
  • 4
  • 8
1

try lo: https://github.com/samber/lo#contains

present := lo.Contains[int]([]int{0, 1, 2, 3, 4, 5}, 5)
gaozhidf
  • 2,621
  • 1
  • 22
  • 17