Map

Map

辞書を引く場面を想像してください:単語(キー)を入力すると、すぐにその定義(値)を見つけられます。最初のページから最後のページまでめくる必要はありません — 目的のページに直接アクセスします。Goのmapはまさにそのような「辞書」です — キーで値を素早くルックアップできるO(1)の時間計算量を持つキー・バリューデータ構造です。


1. コアコンセプト

コンセプト 説明
定義 map[KeyType]ValueType;KeyTypeは比較可能な型でなければならない(スライス、map、funcは不可)
作成 make(map[K]V)またはmap[K]V{}リテラル
追加/更新 m[key] = value(キーが存在しなければ追加、存在すれば上書き)
読み取り v := m[key]
削除 delete(m, key)
comma okパターン v, ok := m[key];キーが存在しなければokはfalse
イテレーション for k, v := range m(ランダム順序、信頼性なし)
長さ len(m)
⚠️ 注意: mapは参照型で、代入とパラメータ渡しはデータのコピーではなく参照を渡します。


2. 基本構文/使い方

Mapの作成

GO
// 方法1:makeを使用
m1 := make(map[string]int)

// 方法2:リテラルで初期化付き作成
m2 := map[string]int{
    "apple":  5,
    "banana": 3,
}

// 方法3:宣言後に代入
var m3 map[string]int        // m3はここでnil。直接代入不可
m3 = make(map[string]int)    // まず初期化
m3["cherry"] = 7             // その後代入
💡 ヒント: var m map[K]Vで宣言したmapはnilです — 書き込むとpanicしますが、読み取り(ゼロ値を返す)とlen()(0を返す)は問題ありません。

CRUD操作

GO
m := map[string]int{"a": 1, "b": 2}

// 作成
m["c"] = 3

// 更新
m["a"] = 10

// 読み取り
v := m["a"]  // v = 10

// 削除
delete(m, "b")
💡 ヒント: 存在しないキーを削除してもエラーにはなりません。何も起こりません。

comma okパターン

GO
m := map[string]int{"x": 42}

v, ok := m["x"]  // v = 42, ok = true
v, ok = m["y"]   // v = 0, ok = false(intのゼロ値を返す)

if _, exists := m["z"]; !exists {
    fmt.Println("キー 'z' は存在しません")
}
💡 ヒント: 値を気にしない場合は、_で無視してください:_, ok := m[key]


3. コード例

例1:基本的な使い方(難易度⭐)

学生の成績テーブルを作成し、CRUD操作を行います。

GO
package main

import "fmt"

func main() {
    // 学生の成績mapを作成
    scores := map[string]int{
        "Alice": 90,
        "Bob":   85,
    }

    // 学生を追加
    scores["Charlie"] = 92

    // Bobの成績を変更
    scores["Bob"] = 88

    // Aliceの成績を検索
    fmt.Println("Aliceの成績:", scores["Alice"])

    // Charlieを削除
    delete(scores, "Charlie")

    // 全学生の成績をイテレーション
    for name, score := range scores {
        fmt.Printf("%s: %d\n", name, score)
    }

    fmt.Println("学生数:", len(scores))
}
▶ 試してみよう

出力例(イテレーション順序は異なる場合があります):

TEXT
Aliceの成績: 90
Bob: 88
Alice: 90
学生数: 2

例2:中級的な使い方(難易度⭐⭐)

comma okパターンによる安全なクエリ、mapのイテレーション、ネストされたmap。

GO
package main

import "fmt"

func main() {
    // ========== comma okパターン ==========
    fruits := map[string]int{
        "apple":  5,
        "banana": 3,
    }

    // 安全なクエリ
    if count, ok := fruits["apple"]; ok {
        fmt.Printf("appleは%d個あります\n", count)
    }

    if count, ok := fruits["grape"]; !ok {
        fmt.Println("grapeは存在しません。リストに追加します")
        fruits["grape"] = 10
    }

    // ========== rangeイテレーション ==========
    fmt.Println("\n全てのフルーツ:")
    for fruit, count := range fruits {
        fmt.Printf("  %s: %d\n", fruit, count)
    }

    // ========== ネストされたmap(mapのmap) ==========
    // クラス -> 学生 -> 成績
    classScores := map[string]map[string]int{
        "Class A": {
            "Alice": 90,
            "Bob":   85,
        },
        "Class B": {
            "Charlie": 92,
            "Diana":   88,
        },
    }

    // ネストされたmapを検索
    if class, ok := classScores["Class A"]; ok {
        if score, ok := class["Alice"]; ok {
            fmt.Printf("\nClass A Aliceの成績: %d\n", score)
        }
    }

    // ネストされたmapをイテレーション
    fmt.Println("\n全クラスの成績:")
    for class, students := range classScores {
        fmt.Printf("  %s:\n", class)
        for name, score := range students {
            fmt.Printf("    %s: %d\n", name, score)
        }
    }
}
▶ 試してみよう

出力:

TEXT
appleは5個あります
grapeは存在しません。リストに追加します

全てのフルーツ:
  apple: 5
  banana: 3
  grape: 10

Class A Aliceの成績: 90

全クラスの成績:
  Class A:
    Alice: 90
    Bob: 85
  Class B:
    Charlie: 92
    Diana: 88

例3:総合応用(難易度⭐⭐⭐)

mapベースのデータ処理による単語頻度カウンターを実装します(ソート出力、高頻度単語の検出)。

GO
package main

import (
    "fmt"
    "sort"
    "strings"
)

// WordCounterはテキスト内の各単語の出現回数をカウントします
func WordCounter(text string) map[string]int {
    // 小文字に変換し、空白で分割
    words := strings.Fields(strings.ToLower(text))

    // 単語頻度mapを作成
    freq := make(map[string]int)

    for _, word := range words {
        // 句読点を除去(簡易処理)
        word = strings.Trim(word, ".,!?;:\"'")
        if word != "" {
            freq[word]++
        }
    }

    return freq
}

// TopNは最も頻度の高いN個の単語を返します(頻度降順)
func TopN(freq map[string]int, n int) []string {
    // mapをソート可能なスライスに変換
    type wordFreq struct {
        word  string
        count int
    }

    // スライスを構築
    pairs := make([]wordFreq, 0, len(freq))
    for w, c := range freq {
        pairs = append(pairs, wordFreq{w, c})
    }

    // 頻度降順でソート
    sort.Slice(pairs, func(i, j int) bool {
        if pairs[i].count == pairs[j].count {
            return pairs[i].word < pairs[j].word // 頻度が同じ場合はアルファベット順
        }
        return pairs[i].count > pairs[j].count
    })

    // 上位N個を取得
    if n > len(pairs) {
        n = len(pairs)
    }

    result := make([]string, n)
    for i := 0; i < n; i++ {
        result[i] = fmt.Sprintf("%s(%d)", pairs[i].word, pairs[i].count)
    }

    return result
}

// MergeFreqは2つの単語頻度mapをマージします
func MergeFreq(a, b map[string]int) map[string]int {
    result := make(map[string]int)

    // aのデータをコピー
    for k, v := range a {
        result[k] = v
    }

    // bのデータを累積
    for k, v := range b {
        result[k] += v
    }

    return result
}

func main() {
    text1 := "Go is great. Go is fast. Go is easy to learn."
    text2 := "I love Go. Go makes programming fun and easy."

    // 単語頻度をカウント
    freq1 := WordCounter(text1)
    freq2 := WordCounter(text2)

    fmt.Println("テキスト1の単語頻度:")
    for word, count := range freq1 {
        fmt.Printf("  %s: %d\n", word, count)
    }

    fmt.Println("\nテキスト2の単語頻度:")
    for word, count := range freq2 {
        fmt.Printf("  %s: %d\n", word, count)
    }

    // 単語頻度をマージ
    merged := MergeFreq(freq1, freq2)

    // 上位5つの高頻度単語を見つける
    fmt.Println("\nマージ後の上位5つの高頻度単語:")
    top5 := TopN(merged, 5)
    for i, item := range top5 {
        fmt.Printf("  %d. %s\n", i+1, item)
    }

    // 総単語数をカウント
    totalWords := 0
    for _, count := range merged {
        totalWords += count
    }
    fmt.Printf("\n総単語数: %d、ユニーク単語数: %d\n", totalWords, len(merged))
}
▶ 試してみよう

出力例:

TEXT
テキスト1の単語頻度:
  go: 3
  is: 3
  great: 1
  fast: 1
  easy: 1
  to: 1
  learn: 1

テキスト2の単語頻度:
  i: 1
  love: 1
  go: 2
  makes: 1
  programming: 1
  fun: 1
  and: 1
  easy: 1

マージ後の上位5つの高頻度単語:
  1. go(5)
  2. is(3)
  3. easy(2)
  4. and(1)
  5. fast(1)

総単語数: 18、ユニーク単語数: 12

3. よくあるユースケース

ケース1:キャッシュ / 高速ルックアップ

mapを使ってシンプルなインメモリキャッシュを実装し、冗長な計算を回避します。

GO
package main

import "fmt"

// フィボナッチ数列(キャッシュ付き)
var cache = map[int]int{0: 0, 1: 1}

func fib(n int) int {
    // まずキャッシュを確認
    if val, ok := cache[n]; ok {
        return val
    }

    // キャッシュミス、計算してキャッシュに格納
    result := fib(n-1) + fib(n-2)
    cache[n] = result
    return result
}

func main() {
    for i := 0; i <= 10; i++ {
        fmt.Printf("fib(%d) = %d\n", i, fib(i))
    }
    fmt.Println("\nキャッシュ内容:", cache)
}

ケース2:グループ統計

mapを使ってデータをグループ化してカウントします。

GO
package main

import "fmt"

func main() {
    // 学生リスト:名前 -> クラス
    students := map[string]string{
        "Alice":   "Class A",
        "Bob":     "Class B",
        "Charlie": "Class A",
        "Diana":   "Class B",
        "Eve":     "Class A",
    }

    // クラス別にグループ化
    groups := make(map[string][]string)
    for name, class := range students {
        groups[class] = append(groups[class], name)
    }

    // グループ化結果を出力
    for class, members := range groups {
        fmt.Printf("%s (%d名): %v\n", class, len(members), members)
    }
}

出力:

TEXT
Class A (3名): [Alice Charlie Eve]
Class B (2名): [Bob Diana]

❓ よくある質問

質問1:なぜmapのイテレーション順序は毎回違うのですか?

Goは意図的にmapのイテレーション順序をランダム化しています。これは開発者がmapのイテレーション順序に依存することを防ぐためで、並行シナリオでは内部構造が変わる可能性があるからです。順序付きイテレーションが必要な場合は、まずキーをスライスに収集し、ソートしてからイテレーションしてください:

GO
keys := make([]string, 0, len(m))
for k := range m {
    keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
    fmt.Println(k, m[k])
}

質問2:mapはスレッドセーフですか?

いいえ。 Goのmapは並行セーフではありません。複数のgoroutineが同じmapに同時に読み書きするとpanicが発生します。解決策:

GO
// sync.Mapの使用
var m sync.Map
m.Store("key", "value")
v, ok := m.Load("key")

質問3:mapのキーとして使える型は何ですか?

mapのキーは比較可能な型でなければなりません。以下を含みます:

キーとして使用できない型:slicemapfunc

質問4:mapに特定のキーが含まれているかチェックするには?

comma okパターンを使用してください:

GO
if _, ok := m[key]; ok {
    // キーが存在する
} else {
    // キーが存在しない
}

値がゼロ値かどうかでキーの存在をチェックしないでください。ゼロ値が有効な値である可能性があるからです。


📖 まとめ


📝 演習

演習1(⭐)

5つのプログラミング言語とその発明年を格納するmapを作成し、以下を実行してください:

  1. 新しい言語を2つ追加
  2. ある言語の年を変更
  3. ある言語を削除
  4. comma okパターンで言語の存在をチェック
  5. 全内容をイテレーションして出力

演習2(⭐⭐)

シンプルな連絡先プログラムを実装してください:

  1. map[string][]stringを定義。キーは連絡先名、値は電話番号のリスト
  2. 関数を実装:連絡先追加、電話番号追加、連絡先検索、連絡先削除
  3. 全連絡先を最初の文字でグループ化して表示する関数を実装
GO
// 期待される出力例:
// A: Alice - [13800001111, 13900002222]
// B: Bob - [13700003333]

演習3(⭐⭐⭐)

学生の成績管理システムを実装してください:

  1. ネストされたmap[string]map[string]float64(クラス -> 学生 -> 成績)を使用
  2. 関数を実装:成績追加、学生の全科目成績検索、クラス平均計算
  3. 関数を実装:全クラスの各科目で最高得点の学生を見つける
  4. 結果をフォーマット済みテーブルとして出力
GO
// 期待される出力例:
// ========== クラス平均 ==========
// Class A: 87.5
// Class B: 91.2
//
// ========== 科目別最高得点 ==========
// 数学: Alice (98.0)
// 英語: Bob (95.0)

次のレッスン

👉 07-struct - 構造体

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%