この記事では、Go言語のマップ(map)について、その基本から使い方、注意点までを網羅的にご紹介します。
プログラミングをしていると、名前と電話番号、商品コードと価格みたいに、ペアでデータを扱いたい場面って結構出てきます。
そんな時に大活躍するのが、Go言語のマップなんです。この記事を読むことで、マップって何?どうやって使うの?といった疑問がスッキリ解消するはずです。
この記事で学べること
- Go言語のマップがどんなものか
- マップの作り方(宣言と初期化の方法)
- マップの基本的な操作(要素の追加、取得、削除)
- マップの中身を順番に見ていく方法
- マップを使う時に気をつけること
Go言語のマップ(map)とは?
Go言語のマップ(map)とは、「キー(key)」と「値(value)」のペアをまとめて管理できるデータ構造のことです。
他のプログラミング言語に触れたことがある方なら、「連想配列」や「辞書」、「ハッシュテーブル」といった名前で似たような機能を聞いたことがあるかもしれませんね。
Go言語のマップ (例: scores) +-----------------------------+ | キー (Key) | 値 (Value) | |--------------|-------------| | "Alice" | 85 | <-- キー "Alice" に 値 85 が対応 | "Bob" | 92 | <-- キー "Bob" に 値 92 が対応 | "Carol" | 78 | <-- キー "Carol" に 値 78 が対応 +-----------------------------+ (※ 中の要素の順番は決まっていません)
例えば、人の名前(キー)とその人の年齢(値)を結びつけて覚えておきたい、といった状況でマップはとても便利です。
キーを指定すれば、それに対応する値をすぐに取り出すことができます。まるで、辞書で単語を引くような感覚でデータにアクセスできるんですよ。
設定情報を保存したり、データの種類ごとに数を数えたりと、プログラムの中でデータを整理整頓したい時に、マップは本当に頼りになる存在です。
Go言語マップ(map)の基本的な書き方
では、実際にGo言語でマップをどうやって書くのか、見ていきましょう。
マップを使うには、まず「こういうマップを作りますよ」と宣言してあげる必要があります。宣言の方法はいくつかあるので、順番に説明しますね。
マップの宣言と初期化(リテラル)
一番よく使われるのが、マップリテラル `map[キーの型]値の型{}` を使う方法です。角括弧 `[]` の中にキーのデータ型を、その後に値のデータ型を書きます。波括弧 `{}` の中に、`キー: 値` の形で初期データを入れることもできますし、空っぽのマップを作ることも可能です。
一番直感的で分かりやすい書き方かもしれません。
書き方
package main import "fmt" func main() { // 文字列をキー、整数を値とするマップを宣言し、初期化 // Key: string, Value: int scores := map[string]int{ "Alice": 85, "Bob": 92, "Carol": 78, } fmt.Println(scores) // マップ全体を出力 // 整数をキー、文字列を値とするマップを宣言 (初期値なし、空のマップ) // Key: int, Value: string users := map[int]string{} fmt.Println(users) // 空のマップを出力 // 空のマップに後から要素を追加することも可能 users[1] = "Suzuki" fmt.Println(users) }
ソースコードの表示結果
map[Alice:85 Bob:92 Carol:78] map[] map[1:Suzuki]
こんな感じで、`map[キーの型]値の型` で型を指定して、`{}` の中に初期値を入れたり、空にしたりしてマップを作ります。
`make`関数を使ったマップの作成
もう一つのマップ作成方法として、Go言語に組み込まれている `make` 関数を使うやり方があります。`make(map[キーの型]値の型)` のように書きます。こちらは、最初から空っぽのマップを作る時に使われることが多いです。
マップリテラル `{}` を使って空のマップを作るのと似ていますが、`make` を使うと、最初にどれくらいの要素が入りそうか、大まかな容量を指定することもできます(ただ、最初はあまり気にしなくても大丈夫!)。
書き方
package main import "fmt" func main() { // make関数を使って、文字列をキー、整数を値とするマップを作成 // Key: string, Value: int ages := make(map[string]int) // 作った直後は空っぽ fmt.Println(ages) fmt.Println(len(ages)) // len() で要素数が取得できる (最初は0) // 要素を追加 ages["Tanaka"] = 30 ages["Sato"] = 25 fmt.Println(ages) fmt.Println(len(ages)) // 要素数は2になる }
ソースコードの表示結果
map[] 0 map[Sato:25 Tanaka:30] 2
どちらの方法でもマップは作れますが、最初からいくつか値を入れたい場合はマップリテラル、空から始める場合はどちらでも、という感じで覚えておくと良いでしょう。
Go言語マップ(map)の実践的な使い方
マップを作ったら、次は実際にデータを入れたり、取り出したり、消したりといった操作が必要になりますよね。
ここでは、マップの基本的な操作方法を一つずつ見ていきましょう。サンプルコードと一緒に動きを確認すると、理解が深まりますよ。
要素の追加と更新
マップに新しいキーと値のペアを追加したり、すでに存在するキーの値を更新したりするのは、とても簡単です。`マップ変数[キー] = 値` の形で書くだけです。
もし指定したキーがマップにまだ存在しなければ、新しい要素として追加されます。もしキーがすでに存在していれば、そのキーに対応する値が新しい値で上書き(更新)されます。追加も更新も同じ書き方なのがポイントです。
書き方
package main import "fmt" func main() { colors := make(map[string]string) // 要素の追加 colors["apple"] = "red" colors["banana"] = "yellow" fmt.Println("追加後:", colors) // 要素の更新 (キー "apple" の値を更新) colors["apple"] = "green" // 赤りんごを青りんごに... fmt.Println("更新後:", colors) // 新しい要素の追加 colors["grape"] = "purple" fmt.Println("さらに追加後:", colors) }
ソースコードの表示結果
追加後: map[apple:red banana:yellow] 更新後: map[apple:green banana:yellow] さらに追加後: map[apple:green banana:yellow grape:purple]
シンプルですよね!
要素の取得
マップから値を取り出すのも簡単です。`マップ変数[キー]` の形で、値を取得したいキーを指定します。
ただし、一つ注意点があります。もし指定したキーがマップの中に存在しなかった場合、エラーにはならず、その値の型の「ゼロ値」が返ってきます。例えば、値が `int` 型なら `0`、`string` 型なら空文字列 `""`、`bool` 型なら `false` といった具合です。
書き方
package main import "fmt" func main() { scores := map[string]int{ "Alice": 85, "Bob": 92, } // キー "Alice" の値を取得 aliceScore := scores["Alice"] fmt.Printf("Aliceのスコア: %d\n", aliceScore) // 存在しないキー "Charlie" の値を取得しようとすると... charlieScore := scores["Charlie"] fmt.Printf("Charlieのスコア: %d\n", charlieScore) // intのゼロ値である 0 が表示される }
ソースコードの表示結果
Aliceのスコア: 85 Charlieのスコア: 0
Charlieさんのスコアは登録していないので、0点が返ってきました。これが意図した0点なのか、それともキーが存在しなかったから0点なのか、区別がつかない場合がありますね。そんな時のための方法が次です。
要素の存在確認 (カンマOKイディオム)
マップから値を取得する際、本当にそのキーが存在したのかどうかを知りたい場合があります。特に、値のゼロ値(`0` や `""` など)が有効なデータとして存在しうる場合に有効です。
そのために使われるのが、「カンマOKイディオム」と呼ばれる書き方です。値を取得する際に、第二の戻り値として、キーが存在したかどうかを示す `bool` 型(`true` か `false`)の値を受け取れます。
`値, ok := マップ変数[キー]` のように書きます。もしキーが存在すれば、`ok` には `true` が、存在しなければ `false` が入ります。キーの存在有無を確実にチェックできるので、非常によく使われるテクニックです。
書き方
package main import "fmt" func main() { scores := map[string]int{ "Alice": 85, "Bob": 92, "David": 0, // Davidさんは本当に0点だったと仮定 } // キー "Bob" の値を取得 (存在する) bobScore, ok1 := scores["Bob"] if ok1 { fmt.Printf("Bobのスコア: %d (存在します)\n", bobScore) } else { fmt.Println("Bobのスコアは見つかりません") } // キー "Charlie" の値を取得 (存在しない) charlieScore, ok2 := scores["Charlie"] if ok2 { fmt.Printf("Charlieのスコア: %d (存在します)\n", charlieScore) } else { // ok2 は false になるので、こちらが実行される fmt.Println("Charlieのスコアは見つかりません") // charlieScore には int のゼロ値 0 が入っている fmt.Printf(" (取得試行時の値: %d)\n", charlieScore) } // キー "David" の値を取得 (存在するが、値は0) davidScore, ok3 := scores["David"] if ok3 { // ok3 は true になる fmt.Printf("Davidのスコア: %d (存在します)\n", davidScore) } else { fmt.Println("Davidのスコアは見つかりません") } }
ソースコードの表示結果
Bobのスコア: 92 (存在します) Charlieのスコアは見つかりません (取得試行時の値: 0) Davidのスコア: 0 (存在します)
この `ok` をチェックすることで、データが存在しない場合と、データがゼロ値の場合を明確に区別できるようになりましたね!
要素の削除
マップから特定のキーとその値のペアを削除したい場合は、組み込みの `delete` 関数を使います。`delete(マップ変数, キー)` のように書きます。
もし指定したキーがマップに存在しなくても、`delete` 関数はエラーを起こしません。何もせずに処理が終わるだけなので、安心して使えます。
書き方
package main import "fmt" func main() { members := map[int]string{ 1: "Alice", 2: "Bob", 3: "Carol", } fmt.Println("削除前:", members) // キー 2 (Bob) の要素を削除 delete(members, 2) fmt.Println("Bob削除後:", members) // 存在しないキー 99 を削除しようとしてもエラーにならない delete(members, 99) fmt.Println("存在しないキー削除試行後:", members) // 変化なし }
ソースコードの表示結果
削除前: map[1:Alice 2:Bob 3:Carol] Bob削除後: map[1:Alice 3:Carol] 存在しないキー削除試行後: map[1:Alice 3:Carol]
これで、不要になったデータをマップからきれいに消すことができます。
マップの繰り返し処理 (`for range`)
マップに含まれているすべてのキーと値のペアを順番に処理したい場合は、`for range` ループを使います。`for キー変数, 値変数 := range マップ変数` のように書くと、ループの各回でマップの要素が一つずつ取り出され、それぞれキー変数と値変数に代入されます。
キーだけが必要な場合や、値だけが必要な場合もありますよね。その場合は、不要な方の変数を `_` (ブランク識別子) にすることで、無視できます。
ここで絶対に覚えておいてほしい超大事なことがあります。`for range` でマップの要素を取り出すとき、その順番は毎回同じとは限りません!実行するたびに順番が変わる可能性があるので、処理の順番に依存するようなプログラムは書かないようにしてください。
書き方
package main import "fmt" func main() { fruits := map[string]string{ "A": "Apple", "B": "Banana", "C": "Cherry", } fmt.Println("--- キーと値を両方取得 ---") // キーと値を取得 for key, value := range fruits { fmt.Printf("キー: %s, 値: %s\n", key, value) } // ↑ 実行するたびに A, B, C の表示順が変わる可能性がある! fmt.Println("\n--- キーだけ取得 ---") // キーだけ取得 (値は無視) for key := range fruits { fmt.Printf("キー: %s\n", key) } fmt.Println("\n--- 値だけ取得 ---") // 値だけ取得 (キーは無視) for _, value := range fruits { fmt.Printf("値: %s\n", value) } }
ソースコードの表示結果(一例。実行ごとに順序が変わる可能性あり)
--- キーと値を両方取得 --- キー: C, 値: Cherry キー: A, 値: Apple キー: B, 値: Banana --- キーだけ取得 --- キー: C キー: A キー: B --- 値だけ取得 --- 値: Cherry 値: Apple 値: Banana
マップの中身をすべてチェックしたい時に `for range` は便利ですが、順番は気まぐれ、と覚えておきましょう。
Go言語マップ(map)の注意点
マップはとても便利な機能ですが、使い方を間違えると予期せぬエラー(パニック)が発生したり、思った通りに動かなかったりすることがあります。
ここでは、特に初心者が陥りやすい注意点を2つ紹介しますね。ここを押さえておけば、より安心してマップを使えるようになります!
【注意点1】ゼロ値 (`nil`) マップへの書き込み
マップ変数を宣言しただけでは、まだマップの実体は作られていません。この状態のマップを「`nil` マップ」と呼びます。`nil` マップは、要素数が 0 であること以外は、ほとんど何もできません。
特に、`nil` マップに要素を追加しようとすると、プログラムが強制終了(パニック)してしまいます。これは非常によくある間違いなので、気をつけましょう。
マップを使う前には、必ずマップリテラル `{}` や `make` 関数を使って、ちゃんと使える状態に初期化してあげる必要があります。
書き方(悪い例と良い例)
package main import "fmt" func main() { // --- 悪い例 (パニックが発生する) --- // var badMap map[string]int // 宣言だけでは nil マップ // fmt.Println(badMap == nil) // true と表示される // badMap["test"] = 1 // ここでパニック! nil マップには書き込めない // --- 良い例 --- // マップリテラルで初期化 goodMap1 := map[string]int{} // 空のマップで初期化 fmt.Println(goodMap1 == nil) // false と表示される goodMap1["test"] = 1 // これはOK! fmt.Println(goodMap1) // make関数で初期化 goodMap2 := make(map[string]int) // make で初期化 fmt.Println(goodMap2 == nil) // false と表示される goodMap2["hello"] = 2 // これもOK! fmt.Println(goodMap2) }
ソースコードの表示結果(悪い例の部分はコメントアウトしています)
false map[test:1] false map[hello:2]
マップを使うときは、「宣言したら、`{}` か `make` で初期化!」と覚えておくと、パニックを防げます。
【注意点2】マップの順序は保証されない
`for range` のセクションでも触れましたが、これは本当に大事なことなので、もう一度!マップの要素を取り出すとき、その順序は保証されていません。
「さっき実行したときは A, B, C の順だったのに、今度は C, A, B になった!」ということは、普通に起こります。これはGo言語のマップの仕様です。
ですから、マップの要素が特定の順番で処理されることを前提としたプログラムを書いてはいけません。
もし、どうしてもキーを特定の順序(例えばアルファベット順)で処理したい場合は、一度マップからすべてのキーを取り出してスライスに入れ、そのスライスをソートしてからループ処理を行う、といった工夫が必要になります。(少し応用的な話なので、ここでは深入りしませんが、そういう方法もある、とだけ覚えておいてください)
マップは「キーを使って高速に値を探す」のが得意なデータ構造であり、要素の順番を保持するのは苦手、と理解しておきましょう。
まとめ
今回はGo言語のマップ(map)について、基本的なことから少し注意が必要な点まで、一通り見てきました。
最後に、今日のポイントをまとめておきましょう。
- マップはキーと値のペアを格納するデータ構造
- 作成にはマップリテラル `{}` か `make` 関数を使う
- 要素の追加・更新は `マップ[キー] = 値`
- 要素の取得は `値, ok := マップ[キー]` で存在確認も忘れずに
- 要素の削除は `delete(マップ, キー)`
- `for range` で繰り返し処理できるが、順序は保証されない
- 使う前には必ず初期化が必要 (`nil` マップへの書き込みはダメ!)
マップはGo言語でプログラムを書く上で欠かせない、強力な武器になります。
最初は少し戸惑うかもしれませんが、実際にコードを書いて動かしてみるのが一番の近道です!この記事が、あなたのGo言語学習の一助となれば、とても嬉しいです。
さあ、マップを使いこなして、もっともっとGo言語プログラミングを楽しんでいきましょう!
【関連記事】 Go言語とは?特徴・できること・将来性
0 件のコメント:
コメントを投稿
注: コメントを投稿できるのは、このブログのメンバーだけです。