こんにちは。Go入門ブログの第15回です。
本記事では、Goにおける参照型データ構造のひとつであるマップについて解説します。
前回の記事では、スライスがGoにおける可変長配列であることを学習しました。
今回学習するマップは、Goにおける連想配列というべきものです。
キーと値とを関連づけて保持するデータ構造で、この関連付けがマップするということであると捉えることができます。
Contents
マップの定義と代入
マップの定義は以下の書式で記述できます。
また、マップの生成にはスライスと同様にmake関数を使用します。
マップには初期状態ではキーが割り当てられていません。
マップ名[キー] = 要素の書式で、キーと要素を同時に代入することができます。
代入済みのキーに重複する代入を行うと、要素の値を上書きすることができます。
それでは実例をみてみましょう。
package main import "fmt" func main() { // 数値型のキーと文字列型の要素を持つマップを定義 var student map[int]string student = make(map[int]string) // 要素を追加 student[1] = "山田太郎" student[10] = "鈴木二郎" student[100] = "佐藤花子" fmt.Println(student) // 要素を上書き student[10] = "吉田和夫" fmt.Println(student) }
このサンプルの実行結果は以下のようになります。
map[1:山田太郎 10:鈴木二郎 100:佐藤花子]
map[1:山田太郎 10:吉田和夫 100:佐藤花子]
マップのリテラル
マップのリテラルは構造体のそれと似ています。
マップの型名の後に{ }で囲い、キーと要素のペアを任意の数だけ列挙することができます。
キーと要素のペアはキーの値 : 要素の値という書式で記述します。
package main import "fmt" func main() { // 県庁所在地マップを生成しリテラルで初期値を記述 prefectural := map[string]string{ "群馬": "前橋市", "栃木": "宇都宮市", "埼玉": "さいたま市", "東京": "新宿区", "神奈川": "横浜市", "千葉": "千葉市", "茨城": "水戸市 ", } fmt.Println(prefectural) }
このサンプルの実行結果は以下のようになります。
map[千葉:千葉市 埼玉:さいたま市 東京:新宿区 栃木:宇都宮市 神奈川:横浜市 群馬:前橋市 茨城:水戸市 ]
マップの要素の参照
マップの要素の値は、通常の配列やスライスと同様に、マップ名[キー]の書式で参照・取得できます。
しかし、マップではキーにも任意の値を代入できる仕様上、配列とは異なる注意点があります。
それは存在しないキーを指定して参照した場合の挙動です。
配列やスライスでは存在しないキーを参照するとエラーになります。
対してマップでは、存在しないキーを指定して参照すると、要素型のゼロ値が取得されます。
内部的には、要素の取得の前に初期値の代入を行っていることになります。
実例をみてみましょう。
package main import "fmt" func main() { // 数値型のキーと要素を持つマップを定義 m := map[int]int{1: 10, 2: 20, 3: 30} // 要素を個別に参照 fmt.Printf("キー1の値:%d \n", m[1]) fmt.Printf("キー2の値:%d \n", m[2]) fmt.Printf("キー3の値:%d \n", m[3]) // 存在しないキーを指定して要素を参照 fmt.Printf("キー9の値:%d \n", m[9]) }
このサンプルの実行結果は以下のようになります。
キー1の値:10
キー2の値:20
キー3の値:30
キー9の値:0
キー9は事前に代入を行っていないにも関わらず、参照を行うと初期値のゼロが代入されていることがわかります。
キーの存在をチェックしつつ要素を取得する
Goのマップには、この問題を回避するための機能もきちんと用意されています。
マップの要素を参照すると、2つの目の値としてキーの存在有無がbool型で返されます。
該当のキーが存在すればtrueが、存在しなければfalseです。
したがって、要素を参照した結果を2つの変数で値を受け取れば、要素の取得と同時にキーの存在チェックを行うことができます。
また、要素は不要でキーの存在有無だけをチェックしたい場合は、一つ目の返り値をアンダースコア(_)で受け取ることで値を破棄できます。
実例を見てみましょう。
package main import "fmt" func main() { // 数値型のキーと要素を持つマップを定義 m := map[int]int{1: 10, 2: 20, 3: 30} var v int var ok bool // 存在するキーを参照 v, ok = m[1] fmt.Println(v, ok) // 存在しないキーを参照 v, ok = m[9] fmt.Println(v, ok) // 値を破棄してキー存在のみをチェック _, ok = m[2] fmt.Println(ok) }
このサンプルの実行結果は以下のようになります。
10 true
0 false
true
要素数の取得と要素の削除
マップもスライスと同様に、len()関数で要素数を取得できます。
ただし、cap()による容量の取得は行えません。
また、組み込み関数deleteによって要素を削除することができます。
delete関数は第1引数に対象のマップを、第2引数に削除したい要素のキー値を指定します。
package main import "fmt" func main() { // 数値型のキーと要素を持つマップを定義 m := map[int]int{1: 10, 2: 20, 3: 30} // マップの要素数を取得 fmt.Println(len(m)) // キー2の要素を削除 delete(m, 2) fmt.Println(m) }
このサンプルの実行結果は以下のようになります。
3
map[1:10 3:30]
マップのループ
マップもスライスなどと同様に、forとrangeを使用してループ処理を行うことができます。
ただし大きな違いで、反復処理がおこなわれる順序は完全に不定です。
実例をみてみましょう。
package main import "fmt" func main() { // 県庁所在地マップを生成しリテラルで初期値を記述 prefectural := map[string]string{ "群馬": "前橋市", "栃木": "宇都宮市", "埼玉": "さいたま市", "東京": "新宿区", "神奈川": "横浜市", "千葉": "千葉市", "茨城": "水戸市 ", } // マップの要素数文ループ for key, value := range prefectural { fmt.Printf("%s: %s\n", key, value) } }
配列やスライスと同じように、rangeを範囲節としたループ処理でキーと要素を扱っています。
このコードを、筆者の環境で2回実行した結果が以下です。
埼玉: さいたま市
東京: 新宿区
神奈川: 横浜市
千葉: 千葉市
茨城: 水戸市
群馬: 前橋市
栃木: 宇都宮市
$ go run main.go
千葉: 千葉市
茨城: 水戸市
群馬: 前橋市
栃木: 宇都宮市
埼玉: さいたま市
東京: 新宿区
神奈川: 横浜市
2回の実行結果で出力順序が異なるうえ、それぞれの実行結果に規則性は見出せません。
このように、マップのループでは処理順序が保障されませんので注意してください。
終わりに
本記事では、Goにおいて連想配列の役割を担うマップについての解説を行いました。
スライスとマップは、Goの参照型の中で特に使用頻度の高いデータ構造です。
ここまでに学習した内容とあわせ、Goの基礎知識はほぼ網羅できたと思います。
これらの知識をベースに、目的にあわせて個別のパッケージやツールなどを使いこなすことで、実用的なアプリケーションが開発できるはずです。
ここまでの記事を基礎知識のリファレンスとして、今後も活用していただければ幸いです。