【问题标题】:Modifying map while iterating over it in Go在 Go 中迭代时修改地图
【发布时间】:2021-10-08 20:32:49
【问题描述】:

鉴于以下代码,我预计会出现无限循环,但循环会在某个点停止。

m := make(map[int]string, 4)
m[0] = "Foo"
    
for k, v := range m {
    m[k+1] = v
}

我无法弄清楚幕后发生了什么,因为不同的执行返回不同的输出。例如,这些是来自不同执行的一些输出:

map[0:Foo 1:Foo 2:Foo 3:Foo 4:Foo 5:Foo 6:Foo 7:Foo]
map[0:Foo 1:Foo]
map[0:Foo 1:Foo 2:Foo]

range 如何在特定点退出循环,退出条件是什么?

【问题讨论】:

标签: dictionary go


【解决方案1】:

Spec: For statements with range clause 表示行为不可预测:

未指定映射的迭代顺序,也不保证从一次迭代到下一次迭代顺序相同。如果在迭代过程中删除了尚未到达的映射条目,则不会产生相应的迭代值。 如果在迭代过程中创建了映射条目,则该条目可能会在迭代过程中生成,也可能会被跳过。对于创建的每个条目以及从一个迭代到下一个迭代,选择可能会有所不同。如果映射为 nil,则迭代次数为 0。

向您所覆盖的地图添加元素,这些条目可能会或可能不会被循环访问,您不应该对此做出任何假设。

【讨论】:

    【解决方案2】:

    基于语言规范:

    如果在迭代过程中创建了映射条目,则该条目可能会在迭代过程中生成,也可能会被跳过。

    所以如果新元素被跳过,for循环最终会结束。

    【讨论】:

      【解决方案3】:

      其他答案已经解释了您使用 sn-p 观察到的行为。

      因为您的标题相当通用,但您的 sn-p 仅涵盖在迭代地图时添加地图条目,所以这里有一个补充示例,应该让您相信在迭代地图时“交叉删除”地图条目是一个坏主意(Playground):

      package main
      
      import "fmt"
      
      func main() {
          m := map[string]int{"foo": 0, "bar": 1, "baz": 2}
          for k := range m {
              if k == "foo" {
                  delete(m, "bar")
              }
              if k == "bar" {
                  delete(m, "foo")
              }
          }
          fmt.Println(m)
      }
      

      spec says:

      未指定映射的迭代顺序,也不保证从一次迭代到下一次迭代顺序相同。 如果在迭代过程中移除了一个尚未到达的map entry,则不会产生对应的迭代值。

      因此,程序会输出map[bar:1 baz:2]map[baz:2 foo:0],但无法分辨是哪个。

      【讨论】:

        猜你喜欢
        • 2015-02-05
        • 2020-10-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-02-24
        • 2013-05-13
        • 2014-09-17
        • 2016-03-26
        相关资源
        最近更新 更多