Go のスライスのイテレーション(for range)

スライスの全要素を順番に処理するには、for range を使います。Go でスライスをループ処理する最も一般的な方法です。

基本的な for range

for range はスライスの各要素に対して、インデックスと値を返します。

fruits := []string{"apple", "banana", "cherry"}

for i, v := range fruits {
    fmt.Printf("%d: %s\n", i, v)
}
// 0: apple
// 1: banana
// 2: cherry

i にはインデックス、v には要素の値が代入されます。

インデックスのみ使う

値が不要な場合は、値の変数を省略できます。

fruits := []string{"apple", "banana", "cherry"}

for i := range fruits {
    fmt.Printf("Index: %d\n", i)
}

1つの変数だけで range を受けると、インデックスが代入されます。

値のみ使う

インデックスが不要な場合は、ブランク識別子 _ を使って無視します。

fruits := []string{"apple", "banana", "cherry"}

for _, v := range fruits {
    fmt.Println(v)
}
// apple
// banana
// cherry

Go では使わない変数を宣言するとコンパイルエラーになるため、_ で明示的に捨てる必要があります。

値はコピーされる

for range で取得する値は、元の要素のコピーです。ループ内で値を変更しても、元のスライスには影響しません。

numbers := []int{1, 2, 3}

for _, v := range numbers {
    v = v * 10 // コピーを変更しているだけ
}

fmt.Println(numbers) // [1 2 3](変更されない)

元のスライスを変更したい場合は、インデックスを使って直接アクセスします。

numbers := []int{1, 2, 3}

for i := range numbers {
    numbers[i] = numbers[i] * 10
}

fmt.Println(numbers) // [10 20 30]

通常の for ループとの比較

for range の代わりに、通常の for ループも使えます。

for range

簡潔で読みやすい。インデックスと値を同時に取得できる。

for i := 0; i < len(s); i++

インデックスを自由に制御できる。逆順や飛ばしループに向く。

numbers := []int{1, 2, 3, 4, 5}

// 逆順で処理
for i := len(numbers) - 1; i >= 0; i-- {
    fmt.Println(numbers[i])
}

// 1つ飛ばしで処理
for i := 0; i < len(numbers); i += 2 {
    fmt.Println(numbers[i])
}

特殊な順序で処理したい場合は、通常の for ループが適しています。

ループ中の要素追加に注意

for range の開始時にスライスの長さが決まるため、ループ中に要素を追加しても、追加した要素は処理されません。

numbers := []int{1, 2, 3}

for i, v := range numbers {
    if i == 0 {
        numbers = append(numbers, 4, 5)
    }
    fmt.Println(v)
}
// 1
// 2
// 3
// (4, 5 は出力されない)

ループ開始時の長さ(3)で繰り返しが決まるため、途中で追加した要素は処理対象になりません。ループ中にスライスを変更する場合は、この挙動を理解しておく必要があります。