Modifying map while iterating over it in Go

huangapple go评论71阅读模式
英文:

Modifying map while iterating over it in Go

问题

给定以下代码,我期望会出现无限循环,但实际上循环在某个点停止了。

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 如何工作以在某个点退出循环,以及退出条件是什么?

英文:

Given the following code I would expected an infinite loop but the loop is being stopped at certain point.

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

I cannot figure out what happen under the hood because different execution return different output. For example these are a few outputs from different executions:

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]

How range works in order to exit from loop at certain point and what is the exit condition?

答案1

得分: 8

Spec: 对于带有 range 子句的语句 中指出行为是不可预测的:

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

在对映射进行迭代时,添加元素到映射中,这些条目可能会被循环访问,也可能不会被访问,你不应该对此做任何假设。

英文:

Spec: For statements with range clause says the behavior is unpredictable:

> The iteration order over maps is not specified and is not guaranteed to be the same from one iteration to the next. If a map entry that has not yet been reached is removed during iteration, the corresponding iteration value will not be produced. If a map entry is created during iteration, that entry may be produced during the iteration or may be skipped. The choice may vary for each entry created and from one iteration to the next. If the map is nil, the number of iterations is 0.

Adding elements to the map you're ranging over, those entries may or may not be visited by the loop, you should not assume anything regarding to that.

答案2

得分: 3

根据语言规范:

如果在迭代过程中创建了一个映射条目,该条目可能在迭代期间产生,也可能被跳过。

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

英文:

Based on the language spec:

> If a map entry is created during iteration, that entry may be produced during the iteration or may be skipped.

So if the new elements are skipped, the for-loop eventually ends.

答案3

得分: 3

其他答案已经解释了你在代码片段中观察到的行为。

因为你的标题比较通用,但是你的代码片段只涵盖了在迭代地图时添加映射条目的情况,这里有一个补充示例,应该能够让你相信在迭代地图时"交叉删除"映射条目是一个不好的主意(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)
}

规范中说:

> 地图的迭代顺序没有指定,并且不能保证从一次迭代到下一次迭代是相同的。如果在迭代过程中删除了尚未到达的地图条目,则相应的迭代值将不会被生成。

因此,程序的输出可能是map[bar:1 baz:2]map[baz:2 foo:0],但无法确定具体是哪个。

英文:

The other answers have already explained the behavior you observe with your snippet.

Because your title is rather generic but your snippet only covers the addition of map entries while iterating over the map, here is a complementary example that should convince you that "cross-removing" map entries while iterating over the map is a bad idea (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)
}

The spec says:

> The iteration order over maps is not specified and is not guaranteed to be the same from one iteration to the next. If a map entry that has not yet been reached is removed during iteration, the corresponding iteration value will not be produced.

As a result, the program outputs either map[bar:1 baz:2] or map[baz:2 foo:0], but there is no way to tell which.

huangapple
  • 本文由 发表于 2021年8月4日 00:03:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/68639365.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定