[Goのはじめ方] Part9: マップ(map)とrangeの使い方

Go

Go言語の強力なデータ構造であるマップと、その反復処理に便利なrangeの使い方を学びます。

1. マップ(map)とは? 🤔

マップは、Go言語に組み込まれている便利なデータ構造の一つで、キー(key)値(value)のペアを格納します。他の言語では連想配列、ハッシュテーブル、ディクショナリなどと呼ばれるものに似ています。

キーを使って対応する値を高速に検索、追加、削除できるのが特徴です。配列やスライスがインデックス(整数)で要素にアクセスするのに対し、マップは様々な型のデータをキーとして使用できます(ただし、キーは比較可能な型である必要があります)。

💡 キーの条件: マップのキーには、==!= 演算子で比較可能な型(数値型、文字列型、ポインタ型、配列型、比較可能なフィールドのみを持つ構造体など)を使用できます。スライス、マップ、関数などはキーとして使用できません。

2. マップの基本的な使い方 🛠️

2.1. マップの宣言と初期化

マップを使うには、まず宣言と初期化を行う必要があります。主な方法は2つあります。

a) `make`関数を使う方法:

make関数を使って空のマップを作成します。キーの型と値の型を指定します。

package main

import "fmt"

func main() {
    // string型のキーとint型の値を持つマップを宣言・初期化
    scores := make(map[string]int)

    fmt.Println(scores) // 出力: map[] (空のマップ)
    fmt.Println(scores == nil) // 出力: false (makeで初期化されたマップはnilではない)

    // var で宣言しただけだと nil マップになる
    var nilMap map[string]int
    fmt.Println(nilMap == nil) // 出力: true
    // nilMap["key"] = 1 // これはランタイムパニックを引き起こす!
}
⚠️ 注意: 宣言だけして初期化されていないマップ (nil マップ) に要素を追加しようとすると、ランタイムパニックが発生します。必ず `make` やリテラルで初期化してから使いましょう。

b) マップリテラルを使う方法:

マップの宣言と同時に初期値を設定できます。

package main

import "fmt"

func main() {
    // 宣言と同時に初期化
    colors := map[string]string{
        "red":   "#FF0000",
        "green": "#00FF00",
        "blue":  "#0000FF", // 末尾のカンマはあってもなくてもOK (推奨)
    }

    fmt.Println(colors) // 出力: map[blue:#0000FF green:#00FF00 red:#FF0000] (順序は保証されない)
}

2.2. 要素の追加と更新

マップに要素を追加したり、既存の要素の値を更新するには、マップ変数[キー] = 値 という構文を使います。

package main

import "fmt"

func main() {
    scores := make(map[string]int)

    // 要素の追加
    scores["math"] = 90
    scores["english"] = 85
    fmt.Println(scores) // 出力例: map[english:85 math:90]

    // 要素の更新
    scores["math"] = 95
    fmt.Println(scores) // 出力例: map[english:85 math:95]
}

2.3. 要素の取得

マップから値を取得するには、マップ変数[キー] という構文を使います。

package main

import "fmt"

func main() {
    scores := map[string]int{
        "math":    95,
        "english": 85,
    }

    mathScore := scores["math"]
    fmt.Println("Math Score:", mathScore) // 出力: Math Score: 95

    // 存在しないキーを指定した場合
    scienceScore := scores["science"]
    fmt.Println("Science Score:", scienceScore) // 出力: Science Score: 0 (int型のゼロ値)
}

存在しないキーを指定して値を取得しようとすると、エラーにはならず、その値型のゼロ値(intなら0, stringなら””, boolならfalseなど)が返ります。

2.4. 要素の存在確認 (Comma ok イディオム)

キーが存在しない場合にゼロ値が返るだけでは、本当に値がゼロなのか、キーが存在しないのか区別できません。そこで、キーの存在を確認するために “comma ok” イディオム を使います。

マップの要素アクセスは、2つの戻り値を返すことができます。

value, ok := mapVariable[key]
  • value: キーに対応する値 (存在しない場合はゼロ値)
  • ok: キーが存在すれば true、存在しなければ false となるbool値
package main

import "fmt"

func main() {
    scores := map[string]int{
        "math":    95,
        "english": 0, // 意図的に0点を設定
    }

    // "english" の存在確認
    englishScore, ok := scores["english"]
    if ok {
        fmt.Printf("English score is %d (Key exists)\n", englishScore)
    } else {
        fmt.Println("English score not found")
    }
    // 出力: English score is 0 (Key exists)

    // "science" の存在確認
    scienceScore, ok := scores["science"]
    if ok {
        fmt.Printf("Science score is %d (Key exists)\n", scienceScore)
    } else {
        fmt.Println("Science score not found") // こちらが出力される
    }
    // 出力: Science score not found

    // 値は不要で、存在確認だけしたい場合
    _, exists := scores["math"]
    if exists {
        fmt.Println("Math key exists")
    }
    // 出力: Math key exists
}

2.5. 要素の削除

マップから要素を削除するには、組み込み関数の delete を使います。

delete(マップ変数, キー)

指定したキーが存在しない場合でも、delete 関数はエラーを起こしません。

package main

import "fmt"

func main() {
    scores := map[string]int{
        "math":    95,
        "english": 85,
        "science": 70,
    }
    fmt.Println("Before delete:", scores) // 出力例: Before delete: map[english:85 math:95 science:70]

    // "english"を削除
    delete(scores, "english")
    fmt.Println("After delete 'english':", scores) // 出力例: After delete 'english': map[math:95 science:70]

    // 存在しないキーを削除してもエラーにならない
    delete(scores, "history")
    fmt.Println("After delete 'history':", scores) // 出力例: After delete 'history': map[math:95 science:70]
}

2.6. マップの要素数

マップに含まれる要素の数(キーと値のペアの数)を取得するには、組み込み関数の len を使います。

package main

import "fmt"

func main() {
    scores := map[string]int{
        "math":    95,
        "science": 70,
    }
    fmt.Println("Number of elements:", len(scores)) // 出力: Number of elements: 2
}

3. rangeによるマップの反復処理 🔄

マップのすべての要素(キーと値のペア)に対して処理を行いたい場合、for ループと range キーワードを組み合わせます。

3.1. キーと値を取得する

package main

import "fmt"

func main() {
    colors := map[string]string{
        "red":   "#FF0000",
        "green": "#00FF00",
        "blue":  "#0000FF",
    }

    fmt.Println("--- Key and Value ---")
    for key, value := range colors {
        fmt.Printf("Key: %s, Value: %s\n", key, value)
    }
    // 出力例 (順序は実行ごとに変わる可能性があります):
    // Key: blue, Value: #0000FF
    // Key: red, Value: #FF0000
    // Key: green, Value: #00FF00
}

3.2. キーのみを取得する

値を使わない場合は、値を受け取る変数を省略できます。

package main

import "fmt"

func main() {
    colors := map[string]string{
        "red":   "#FF0000",
        "green": "#00FF00",
        "blue":  "#0000FF",
    }

    fmt.Println("\n--- Only Keys ---")
    for key := range colors {
        fmt.Printf("Key: %s\n", key)
    }
    // 出力例 (順序は実行ごとに変わる可能性があります):
    // Key: red
    // Key: green
    // Key: blue
}

3.3. 値のみを取得する

キーを使わない場合は、ブランク識別子 _ を使ってキーを無視します。

package main

import "fmt"

func main() {
    colors := map[string]string{
        "red":   "#FF0000",
        "green": "#00FF00",
        "blue":  "#0000FF",
    }

    fmt.Println("\n--- Only Values ---")
    for _, value := range colors {
        fmt.Printf("Value: %s\n", value)
    }
    // 出力例 (順序は実行ごとに変わる可能性があります):
    // Value: #00FF00
    // Value: #0000FF
    // Value: #FF0000
}
🚨 最重要注意点: range の順序
マップを range で反復処理する際、要素が取り出される順序は保証されません。プログラムを実行するたびに順序が変わる可能性があります。特定の順序で処理したい場合は、キーをスライスに格納し、そのスライスをソートしてからループ処理を行うなどの工夫が必要です。
package main

import (
	"fmt"
	"sort"
)

func main() {
	scores := map[string]int{
		"math":    95,
		"english": 85,
		"science": 70,
		"art":     90,
	}

	// 1. キーをスライスに格納
	var keys []string
	for k := range scores {
		keys = append(keys, k)
	}

	// 2. キーのスライスをソート (文字列として)
	sort.Strings(keys)

	fmt.Println("--- Sorted by Key ---")
	// 3. ソートされたキーの順序でマップにアクセス
	for _, key := range keys {
		fmt.Printf("Key: %s, Value: %d\n", key, scores[key])
	}
	// 出力 (常にこの順序):
	// Key: art, Value: 90
	// Key: english, Value: 85
	// Key: math, Value: 95
	// Key: science, Value: 70
}

4. マップの活用例 💡

マップは様々な場面で役立ちます。簡単な例をいくつか見てみましょう。

4.1. 単語の出現回数カウント

package main

import (
	"fmt"
	"strings"
)

func main() {
	text := "apple banana apple orange banana apple"
	words := strings.Fields(text) // スペースで単語に分割

	wordCounts := make(map[string]int)

	for _, word := range words {
		// カンマokイディオムで存在確認しつつカウントアップ
		// _, ok := wordCounts[word]
		// if ok {
		//  wordCounts[word]++
		// } else {
		//  wordCounts[word] = 1
		// }
		// ↑は以下のようにシンプルに書ける
		wordCounts[word]++ // キーが存在しなければゼロ値(0)に+1され、存在すれば現在の値に+1される
	}

	fmt.Println(wordCounts) // 出力例: map[apple:3 banana:2 orange:1]
}

4.2. 設定情報の管理

package main

import "fmt"

func main() {
	config := map[string]string{
		"api_key":    "your_secret_key",
		"endpoint":   "https://api.example.com",
		"timeout_ms": "5000",
	}

	endpoint, ok := config["endpoint"]
	if ok {
		fmt.Println("API Endpoint:", endpoint)
	} else {
		fmt.Println("Endpoint not configured")
	}
	// 出力: API Endpoint: https://api.example.com
}

5. まとめ 🎉

今回はGo言語のマップ(map)と、その要素を反復処理するための range の使い方について学びました。

  • マップはキーと値のペアを格納するデータ構造です。
  • make関数やマップリテラルで初期化します。
  • マップ[キー] で値にアクセスし、delete で要素を削除します。
  • キーの存在確認には “comma ok” イディオム を使いましょう。
  • for key, value := range マップ で要素を反復処理できます。
  • range での反復順序は保証されない 点に注意が必要です。

マップはGoプログラミングにおいて非常に頻繁に使われる重要な機能です。基本的な操作と注意点をしっかり押さえて、様々な場面で活用していきましょう!💪

次は Step 3 に進み、「関数定義と引数・戻り値」について学びます。お楽しみに!

コメント

タイトルとURLをコピーしました