【问题标题】:Does Go have "if x in" construct similar to Python?Go 是否有类似于 Python 的“if x in”构造?
【发布时间】:2013-02-25 18:07:00
【问题描述】:

如果没有遍历整个数组,我如何使用 Go 检查数组中是否有 x?语言有结构吗?

喜欢 Python:if "x" in array: ...

【问题讨论】:

  • AFAIK,在 go 中没有速记。在内部,python 也迭代数组,没有绕过那个。
  • 顺便说一句,重要的一点是,没有办法这样做(如你所愿)“[w]没有迭代在整个阵列上”。使此类循环显式(或在诸如strings.Index 之类的函数后面)有助于使代码的作用更加明显。我的印象是,也许您认为 Python 的 in array: 正在做一些快速/神奇的事情。事实并非如此。明确循环有助于让作者(和所有读者)意识到并考虑其他实现(例如地图)。
  • 不过,如果"x" in set确实很快。
  • 我认为我们不是在寻求一种以编程方式快速的方法,我们只是在寻求一种简洁的方法(但仍然没有......)

标签: if-statement go


【解决方案1】:

刚刚有一个类似的问题,并决定尝试一下这个帖子中的一些建议。

我对 3 种查找类型的最佳和最坏情况进行了基准测试:

  • 使用地图
  • 使用列表
  • 使用 switch 语句

函数代码如下:

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
}

最佳情况选择列表中的第一项,最坏情况使用不存在的值。

结果如下:

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 一路赢,最坏情况比最好情况快得多。

地图最差,列表更接近切换。

所以道德是: 如果你有一个静态的、相当小的列表,那么 switch 语句是不错的选择。

【讨论】:

  • 不知道 Go 是否优化了这种情况,但是如果将 list/map 的初始化移到测试函数之外,是否有区别?
  • 我很想知道比较将如何与排序列表进行比较,以及排序是否值得
  • switch语句中的:而不是,呢?它是否使它更快?
  • 我尝试使用多个 case 语句而不是单个案例。两个函数的结果明显相同。
  • 没有什么比提供解决方案并使用基准将其与最“明显”的其他解决方案进行比较 - 用 data 支持您的建议,而不是猜测!谢谢——我会坚持使用开关,因为我有固定的、静态的检查数量……
【解决方案2】:

这与 Python 的“in”运算符的自然感觉非常接近。您必须定义自己的类型。然后,您可以通过添加像“has”这样的方法来扩展该类型的功能,该方法的行为就像您希望的那样。

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

我有一个实用程序库,我在其中为几种类型的切片定义了一些常见的东西,比如包含整数或我自己的其他结构的切片。

是的,它以线性时间运行,但这不是重点。重点是询问和了解 Go 有哪些通用语言结构,哪些没有。这是一个很好的锻炼。这个答案是愚蠢还是有用取决于读者。

【讨论】:

    【解决方案3】:

    上面使用排序的例子很接近,但在字符串的情况下,只需使用 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

    【讨论】:

    • 这个答案看起来像是下面答案的信息量较少的复制粘贴,它发生在这个答案之前。
    • @cytinus 你指的是什么答案?这是我根据sort.SearchStrings看到的唯一一个。
    • 我在大片中重复搜索的速度提高了 100 倍。
    【解决方案4】:

    另一种选择是使用地图作为集合。您只使用键,并将值设为始终为真的布尔值。然后您可以轻松检查地图是否包含密钥。如果您需要集合的行为,这很有用,如果您多次添加一个值,它只会在集合中出现一次。

    这是一个简单的示例,我将随机数作为键添加到地图中。如果多次生成相同的数字也没关系,它只会在最终地图中出现一次。然后我使用一个简单的 if 检查来查看某个键是否在地图中。

    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

    【讨论】:

      【解决方案5】:

      如果列表包含静态值,则另一种解决方案。

      例如:从有效值列表中检查有效值:

      func IsValidCategory(category string) bool {
          switch category {
          case
              "auto",
              "news",
              "sport",
              "music":
              return true
          }
          return false
      }
      

      【讨论】:

      • 是的,如果您的“有效值”来自数据库怎么办?
      • 是的,这很简洁,但前提是这些值可以提前定义。
      • @RonanDejhero 然后我可以使用 WHERE :myValue IN (subquery) :)
      • 与最佳答案相比,这很整洁
      • 正如其他 cmets 提到的,这对于值是常量的情况非常有用!否则,我推荐接受的答案。
      【解决方案6】:

      这是从“Go 编程:为 21 世纪创建应用程序”一书中引述的:

      使用像这样的简单线性搜索是未排序的唯一选择 数据并且适用于小切片(最多数百个项目)。但对于 更大的切片——特别是如果我们重复执行搜索—— 线性搜索效率很低,平均需要一半的项目 每次都要比较。

      Go 提供了一个使用二分查找的 sort.Search() 方法 算法:这需要只比较 log2(n) 个项目(其中 n 是项目数)每次。从这个角度来看,一个 1000000 个项目的线性搜索平均需要 500000 次比较, 最坏的情况是 1000000 次比较;二分搜索需要在 最多 20 次比较,即使在最坏的情况下也是如此。

      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

      【讨论】:

      • 这只有在您重复搜索时才有意义。否则,排序和二分搜索的复杂度为 n*log(n)*log(n),而线性搜索只有 n。
      • 实际上只是n*log(n) + log(n),因为它是两个随之而来的独立操作
      【解决方案7】:

      Go 中没有内置的运算符来执行此操作。您需要遍历数组。您可以编写自己的函数来执行此操作,如下所示:

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

      如果您希望能够在不遍历整个列表的情况下检查成员资格,则需要使用映射而不是数组或切片,如下所示:

      visitedURL := map[string]bool {
          "http://www.google.com": true,
          "https://paypal.com": true,
      }
      if visitedURL[thisSite] {
          fmt.Println("Already been here.")
      }
      

      【讨论】:

      • 有没有办法在不指定类型的情况下做到这一点?假设我只想要一个通用的 needleInHaystack(needle, haystack) 函数,而对每种类型都没有单独的方法
      • 这可以通过 reflect 包来完成,但是效率相当低(可能和你用 Python 这样的动态语言编写它一样慢)。除此之外,没有。这就是人们说 Go 没有泛型时的意思。
      • 任何偶然发现此答案的人都必须注意,您不能对地图进行排序。使用 go maps 的一大缺点。
      • 您也不能在 Javascript 中对地图(对象)进行排序。这是一个 v8 bug,对象按字母顺序返回值。
      • 地图在大多数语言中都没有排序——这对于地图数据结构(hashmap)的课程来说是一样的。
      猜你喜欢
      • 1970-01-01
      • 2011-11-14
      • 2014-06-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多