'Check if a value is in a list

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?



Solution 1:[1]

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}

Solution 2:[2]

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

Solution 3:[3]

Using generics in Go >= 1.18

package slice

func In[Item compareable](items []Item, item Item) bool {
   for i := 0; i < len(items); i++ {
     if items[i] == item {
        return true
     }
   }
   return false
}
package main

import "fmt"
import "slice"

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

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Zombo
Solution 3 Neil