Go のマップのイテレーション(for range)

マップの全要素を処理するには for range を使う。スライスのイテレーションと似ているが、いくつか重要な違いがある。

基本的なイテレーション

for range でキーと値を同時に取得できる。

ages := map[string]int{
    "Alice":   30,
    "Bob":     25,
    "Charlie": 35,
}

for name, age := range ages {
    fmt.Printf("%s is %d years old\n", name, age)
}

出力例:

Bob is 25 years old
Alice is 30 years old
Charlie is 35 years old

順序は保証されない

マップのイテレーション順序は不定だ。同じマップでも実行するたびに順序が変わる可能性がある。

for i := 0; i < 3; i++ {
    fmt.Println("--- Run", i+1, "---")
    for k, v := range ages {
        fmt.Println(k, v)
    }
}

Go は意図的にマップの順序をランダム化している。順序に依存したコードを書かないようにするためだ。

キーだけを取得

値が不要な場合は、キーだけを受け取れる。

for name := range ages {
    fmt.Println(name)
}

値だけを取得

キーが不要な場合は、ブランク識別子を使う。

for _, age := range ages {
    fmt.Println(age)
}

ソートしてイテレーション

順序が必要な場合は、キーをスライスに取り出してソートする。

import "sort"

ages := map[string]int{
    "Alice":   30,
    "Bob":     25,
    "Charlie": 35,
}

// キーをスライスに取り出す
names := make([]string, 0, len(ages))
for name := range ages {
    names = append(names, name)
}

// ソート
sort.Strings(names)

// ソート順でイテレーション
for _, name := range names {
    fmt.Printf("%s: %d\n", name, ages[name])
}

出力:

Alice: 30
Bob: 25
Charlie: 35

イテレーション中の変更

イテレーション中に要素を追加・削除することは可能だが、挙動が複雑になる。

m := map[int]bool{1: true, 2: true, 3: true}

for k := range m {
    if k == 2 {
        delete(m, k) // 削除はOK
    }
}
fmt.Println(m) // map[1:true 3:true]

削除は安全に行えるが、追加した要素がそのイテレーションで処理されるかどうかは不定だ。

m := map[int]bool{1: true}

for k := range m {
    m[k+10] = true // 追加
    if k > 100 {
        break // 無限ループ防止
    }
}
// 結果は不定

イテレーション中の追加は避けるか、新しいマップに書き込む方が安全だ。

空のマップのイテレーション

空のマップや nil マップをイテレーションしても panic は起きない。単にループが実行されないだけだ。

var m map[string]int // nil マップ

for k, v := range m {
    fmt.Println(k, v) // 実行されない
}

fmt.Println("Done") // 正常に出力される

これにより、マップが空かどうかを事前にチェックする必要がない。