Go言語のrangeを使ってスライスやマップを反復処理する方法についてわかりやすく解説

スポンサーリンク
スポンサーリンク

rangeの概要

データ構造の反復処理 Goの予約語

range

概要rangeは、スライス、配列、マップ、チャネルなどのデータ構造を反復処理するためのキーワードです。

  • rangeを使うと、データ構造の要素を簡潔に処理できる。
  • スライスや配列では、インデックスと要素の2つの値を返す。
  • マップでは、キーと値のペアを返す。
  • チャネルでは、受信した値を取得できる。

スライスと配列のrangeループ

スライスや配列に対してrangeを使うと、インデックスと値の2つの変数を受け取れます。

package main

import "fmt"

func main() {
    numbers := []int{10, 20, 30, 40}

    for index, value := range numbers {
        fmt.Printf("インデックス: %d, 値: %d\n", index, value)
    }
}

解説:

  • range numbersを使うと、スライスの各要素を順番に処理できる。
  • indexにはインデックスが、valueには要素の値が入る。

実行結果:

インデックス: 0, 値: 10
インデックス: 1, 値: 20
インデックス: 2, 値: 30
インデックス: 3, 値: 40

インデックスを無視する

インデックスが不要な場合は、アンダースコア(_)を使って無視できます。

package main

import "fmt"

func main() {
    numbers := []int{10, 20, 30, 40}

    for _, value := range numbers {
        fmt.Println("値:", value)
    }
}

解説:

  • _を使うことで、インデックスを無視できる。
  • 変数を省略することで、不要な変数を定義せずに済む。

実行結果:

値: 10
値: 20
値: 30
値: 40

マップのrangeループ

マップに対してrangeを使うと、キーと値のペアを取得できます。

package main

import "fmt"

func main() {
    ages := map[string]int{"太郎": 25, "花子": 30, "次郎": 20}

    for key, value := range ages {
        fmt.Printf("名前: %s, 年齢: %d\n", key, value)
    }
}

解説:

  • マップをrangeで反復処理すると、キーと値を取得できる。
  • マップの順序は保証されないため、出力順は実行ごとに異なる可能性がある。

実行結果(順序はランダム):

名前: 花子, 年齢: 30
名前: 太郎, 年齢: 25
名前: 次郎, 年齢: 20

キーまたは値を無視する

キーまたは値が不要な場合も、アンダースコア(_)を使って無視できます。

package main

import "fmt"

func main() {
    ages := map[string]int{"太郎": 25, "花子": 30, "次郎": 20}

    // 値のみ使用
    for _, value := range ages {
        fmt.Println("年齢:", value)
    }

    // キーのみ使用
    for key := range ages {
        fmt.Println("名前:", key)
    }
}

解説:

  • for _, value := range ages でキーを無視して値のみ取得。
  • for key := range ages で値を無視してキーのみ取得。

文字列のrangeループ(rune単位)

文字列に対してrangeを使うと、各文字(rune)を取得できます。

package main

import "fmt"

func main() {
    str := "こんにちは"

    for index, char := range str {
        fmt.Printf("インデックス: %d, 文字: %c\n", index, char)
    }
}

解説:

  • rangeを使うと、UTF-8の各文字(rune)を取得できる。
  • マルチバイト文字(日本語など)は、1文字が1バイトとは限らない。

実行結果:

インデックス: 0, 文字: こ
インデックス: 3, 文字: ん
インデックス: 6, 文字: に
インデックス: 9, 文字: ち
インデックス: 12, 文字: は

チャネルのrangeループ

チャネルをrangeで反復処理すると、送信された値を受信し続けることができます。

package main

import "fmt"

func main() {
    ch := make(chan int, 3)
    ch <- 10
    ch <- 20
    ch <- 30
    close(ch) // チャネルをクローズ

    for val := range ch {
        fmt.Println("受信:", val)
    }
}

解説:

  • チャネルがクローズされるまで、rangeで値を受信し続ける。
  • クローズされるとrangeループが自動的に終了する。

実行結果:

受信: 10
受信: 20
受信: 30

注意事項

  • マップの順序は保証されない: ループごとに異なる順番で処理される可能性がある。
  • 文字列のrangeはバイト単位ではなくrune単位: 日本語などのマルチバイト文字を正しく処理できる。
  • チャネルのrangeはクローズを検知: クローズされるまでループし続ける。

よくある質問

Q: rangeを使うと配列やスライスのコピーが作成されますか?
A: いいえ。rangeは要素へのアクセスを提供するだけで、新しいコピーを作成しません。
Q: マップのrangeの順序は一定ですか?
A: いいえ。Goのマップは順序が保証されていないため、実行するたびに異なる順番で出力される可能性があります。
Q: 文字列をrangeでループすると、なぜインデックスが飛ぶのですか?
A: 文字列はUTF-8エンコードされており、マルチバイト文字(日本語など)は1文字が1バイトとは限らないためです。
Q: rangeでスライスを変更できますか?
A: 可能ですが、ループ内で値を変更しても元のスライスには反映されません。スライスのインデックスを使って変更する必要があります。
Q: チャネルのrangeはいつ終了しますか?
A: チャネルが明示的にclose()されたときにループが終了します。未クローズのチャネルをrangeでループすると、データが送信されるまでブロックされ続けます。

まとめ

  • rangeを使うと、スライス、配列、マップ、チャネルを簡単に反復処理できる。
  • スライスや配列では、インデックスと値を取得できる。
  • マップでは、キーと値を取得できるが、順序は保証されない。
  • 文字列のrangeはrune単位で処理され、マルチバイト文字に対応している。
  • チャネルのrangeは、チャネルがクローズされるまで値を受信し続ける。