user1529891
user1529891

Reputation:

Does Go have "if x in" construct similar to Python?

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

Upvotes: 492

Views: 486362

Answers (9)

andybalholm
andybalholm

Reputation: 16100

Since Go 1.18 or newer, you can use slices.Contains.

Before Go 1.18 there was no built-in operator. You needed to iterate over the array. You had to 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
}

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.")
}

Upvotes: 558

go je jo
go je jo

Reputation: 391

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

Upvotes: 10

gaozhidf
gaozhidf

Reputation: 2789

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

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

Upvotes: 3

Igor
Igor

Reputation: 479

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.

Upvotes: 47

IcarusNM
IcarusNM

Reputation: 991

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.

Upvotes: 16

Robert Weber
Robert Weber

Reputation: 299

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

Upvotes: 29

nobled
nobled

Reputation: 3071

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

Upvotes: 8

sebest
sebest

Reputation: 2199

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
}

Upvotes: 169

AlexTT
AlexTT

Reputation: 1003

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

Upvotes: 59

Related Questions