ChatGPT解决这个技术问题 Extra ChatGPT

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
AFAIK, There is not shorthand for that in go. Internally, python also iterates over the array, there is no going around that.
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).
However, if "x" in set is indeed very fast.
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...)

a
andybalholm

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

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
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.
Anyone stumbled upon this answer must note that you CANNOT sort maps. Big downside to using go maps.
you can't sort maps (objects) in Javascript also. It's a v8 bug that objects return values in alphabetically sorted order.
maps aren't sorted in most languages -- that's par for the course for a map data structure (hashmap).
s
sebest

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
}

Yeah, what if your "valid values" come from a database?
Yeah, this is neat but only when these values can be defined in advance.
@RonanDejhero then i could use WHERE :myValue IN (subquery) :)
This is neat comparing to the top answer
as the other comments mention, this is great for the case where the values are constants! Otherwise, I recommend the accepted answer.
A
AlexTT

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


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.
It's actually just n*log(n) + log(n), as it's two consequent independent operations
W
Wai Ha Lee

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.


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?
I'm curious to see how the comparison would play out with a sorted list and whether the sort would be worth it
What about : instead of , in the switch statement? Does it make it faster?
I tried using multiple case statements instead of a single case. Results are sensibly the same with both functions.
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...
s
shadyyx

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


This answer looks like a less informative copy paste of the answer below, which happened before this answer.
@cytinus What answer are you referring to? That's the only one I see based on sort.SearchStrings.
I got a 100x speedup on repeated searches through a large slice.
I
IcarusNM

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.


n
nobled

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


g
go je jo

In Go 1.18+, you can now declare generic Contains function which is also implemented in the experimental slice function. 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
if slices.Contains(arr, "x") {
    // do something
}

which I found here