9

Does Go have something similar to Python's in keyword? I want to check if a value is in a list.

For example in Python:

x = 'red'

if x in ['red', 'green', 'yellow', 'blue']:
    print "found"
else:
    print "not found"

In Go I've come up with using the set idiom but I don't think it's ideal as I have to specify a int value that I'm not using.

x := "red"

valid := map[string]int{"red": 0, "green": 0,"yellow": 0, "blue": 0}

if _, ok := valid[x]; ok {
    fmt.Println("found")
} else {
    fmt.Println("not found")
}

I understand having an in keyword is probably related to generics. Is there a way to do this using go generate or something?

icza
  • 389,944
  • 63
  • 907
  • 827
Neil
  • 8,925
  • 10
  • 44
  • 49
  • 1
    Also see [http://stackoverflow.com/questions/15323767/how-to-if-x-in-array-in-golang](http://stackoverflow.com/questions/15323767/how-to-if-x-in-array-in-golang) – IamNaN May 26 '15 at 07:50
  • any reason why it was not closed as a duplicate? – Salvador Dali May 26 '15 at 08:03

3 Answers3

22

You can use a map[string]bool as a set. When testing and a key is not in the map, the zero value for bool is returned which is false.

So fill the map with the valid values as keys and true as value. If a tested key-value is in the map, its stored true value will be the result. If a tested key-value is not in the map, the zero value for the value type is returned which is false.

Using this, the test becomes this simple:

valid := map[string]bool{"red": true, "green": true, "yellow": true, "blue": true}

if valid[x] {
    fmt.Println("found")
} else {
    fmt.Println("not found")
}

Try it on the Go Playground (with the variants mentioned below).

This is mentioned in the blog post: Go maps in action: Exploiting zero values

Note:

If you have many valid values, since all the values to be stored in the map are true, it may be more compact to use a slice to list the valid values and use a for range loop to initialize your map, something like this:

for _, v := range []string{"red", "green", "yellow", "blue"} {
    valid[v] = true
}

Note #2:

If you don't want to go with the for range loop initialization, you can still optimize it a little by creating an untyped (or bool-typed) one-letter const:

const t = true
valid := map[string]bool{"red": t, "green": t, "yellow": t, "blue": t}
icza
  • 389,944
  • 63
  • 907
  • 827
  • For large sets, using a map[string]struct{} instead of a map[string]bool might save space because the empty struct occupies zero space. Testing for existence needs to use the two-value form `_, exists := valid[x]`. Create the map in the first place using empty struct literals `{}` instead of the bool `true`. – Rick-777 May 26 '15 at 15:34
1

I think map[string]bool in the other answer is a good option. Another method is map[string]struct{}, which uses slightly less memory:

package main

func main() {
   x, valid := "red", map[string]struct{}{
      "red": {}, "green": {}, "yellow": {}, "blue": {},
   }
   if _, ok := valid[x]; ok {
      println("found")
   } else {
      println("not found")
   }
}

You could also wrap it in a type:

package main

type set map[string]struct{}

func newSet(slice []string) set {
   s := make(set)
   for _, each := range slice {
      s[each] = struct{}{}
   }
   return s
}

func (s set) has(v string) bool {
   _, ok := s[v]
   return ok
}

func main() {
   x := "red"
   if newSet([]string{"red", "green", "yellow", "blue"}).has(x) {
      println("found")
   } else {
      println("not found")
   }
}
Zombo
  • 1
  • 62
  • 391
  • 407
0

Using generics in Go >= 1.18

package main

import "fmt"

// In is a generic function that checks if a given item exists in a slice of
// items of a comparable type. It returns true if the item is found, otherwise
// it returns false.
func In[Item comparable](items []Item, item Item) bool {
    for i := 0; i < len(items); i++ {
        if items[i] == item {
            return true
        }
    }
    return false
}

func main() {
    fmt.Println(In([]int{1, 2, 3, 4, 5}, 3))
}
true

Go Playground https://go.dev/play/p/ovhlcaBz0Uc

Neil
  • 8,925
  • 10
  • 44
  • 49