JavaScriptを有効にしてください

golangにおけるmapの使い方

 ·   ·  ☕ 15 分で読めます  ·  🐈 もふもふ

golangにおけるmapの使い方

golangのmapはPHPのようなインタプリタ型言語の連想配列と比べると事前準備が必要なこともあり少し使いにくいです。
特にmapのmapのような使い方をしようと考えた時に悩む方が多いのではないでしょうか。
本記事ではmapの基本的な使い方から注意点などを解説します。

プログラム全体は最後に載せてあります。
コピーしてGo Playground に貼り付ければ実行できますので、実際の動作を確認してみてください。

解説

mapの作成

mapは組み込み関数であるmakeを使用して作成します。

map作成時のmake関数は二つの引数を指定します。

  • 第1引数:型を指定
  • 第2引数:サイズを指定(デフォルトは0)

あらかじめ使用するサイズが決まっている場合は指定しておきましょう。
指定したサイズの範囲内であればメモリの拡張がなくなるため、パフォーマンスが向上します。

第2引数に何も指定しない場合は0を指定した場合と同じになります。

では、色々な型におけるmapの作成事例を解説していきます。

mapの作成事例

map[int]string

mapのキーにint型を指定した例です。

1
2
3
4
5
6
7
catMap := make(map[int]string)
catMap[0] = "みけ猫"
catMap[1] = "キジ猫"
catMap[2] = "ぶち猫"
catMap[3] = "柴犬"

fmt.Println(catMap)
実行結果
1
map[0:みけ猫 1:キジ猫 2:ぶち猫 3:柴犬]

シンプルで分かりやすいですね。
特に問題なく理解できると思います。
柴犬は気にしないでください。

map[time.Time]string

mapのキーに構造体を指定した例です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Jst, err := time.LoadLocation("Asia/Tokyo")
if err != nil {
	panic(err)
}
catDay, err := time.ParseInLocation("2006-01-02", "2022-02-22", Jst)
if err != nil {
	panic(err)
}
emperorsBirthday, err := time.ParseInLocation("2006-01-02", "2022-02-23", Jst)
if err != nil {
	panic(err)
}
springDay, err := time.ParseInLocation("2006-01-02", "2022-03-21", Jst)
if err != nil {
	panic(err)
}
springDayUtc, err := time.ParseInLocation("2006-01-02", "2022-03-21", time.UTC)
if err != nil {
	panic(err)
}
anniversaryDaysMap := make(map[time.Time]string)
anniversaryDaysMap[catDay] = "猫の日"
anniversaryDaysMap[emperorsBirthday] = "天皇誕生日"
anniversaryDaysMap[springDay] = "春分の日"
anniversaryDaysMap[springDayUtc] = "春分の日(UTC)"

fmt.Println(anniversaryDaysMap)
実行結果
1
2
3
4
map[	2022-02-22 00:00:00 +0900 JST:猫の日 
	2022-02-23 00:00:00 +0900 JST:天皇誕生日 
	2022-03-21 00:00:00 +0900 JST:春分の日 
	2022-03-21 00:00:00 +0000 UTC:春分の日(UTC)]

※見やすくするために改行しています

「構造体でもキーにできるんだ」という感じですが、できます。
後述しますが、各メンバ変数の型が「==」演算子で比較可能な型であればmapのキーとして指定できます。

キーの判定についてですが、構造体の場合、各メンバ変数の値が一致するかどうかで判定しますので、当然メンバ変数の値が異なれば別のキーになります。
春分の日の例で言うと、タイムゾーンが違っているので別のキーとして扱われていることが分かると思います。

map[interface{}]string

mapのキーにinterface型を指定した例です。

1
2
3
4
5
6
mixCatMap := make(map[interface{}]string)
mixCatMap[0] = "みけ猫"
mixCatMap["kiji"] = "キジ猫"
mixCatMap[catDay] = "ねこの日"

fmt.Println(mixCatMap)
実行結果
1
2
3
map[	0:みけ猫
	kiji:キジ猫
	2022-02-22 00:00:00 +0900 JST:ねこの日]

※見やすくするために改行しています

interface型をキーにすると様々な型を指定できてとても便利ですが、処理速度が要求される場面では使用を避けましょう。
interface型は内部で型チェックが行われるため低速です。

インタプリタ型言語の連想配列を扱っている方は、interface型は馴染みやすいと思います。
ただ、interface型をキーにすると可読性が下がるため、個人的にはあまり使用したくないですが😅

map[*int]string

mapのキーにint型のポインタを指定した例です

1
2
3
4
5
6
7
8
9
catPointerMap := make(map[*int]string)
mike := 0
buti := 1
kiji := 2
catPointerMap[&mike] = "みけ猫"
catPointerMap[&kiji] = "キジ猫"
catPointerMap[&buti] = "ぶち猫"

fmt.Println(catPointerMap)
実行結果
1
2
3
map[	0xc000130048:みけ猫
	0xc000130050:キジ猫
	0xc000130058:ぶち猫]

※見やすくするために改行しています

ポイント型も指定できちゃいます。

この場合キーはint型ではなくそのポインタのため、変数に入っている0,1,2といった値が関係ないことに注意してください。
仮に値が全て0でもポインタが異なるので、それぞれ別物として扱われます。

このように一応ポインタ型も指定できますが、基本的に使用しない方がいいでしょう。
ポインタがキーというのは分かりにくいですから、バグの温床になります。

有効なユースケースを思いついたら、また別で記事を書きたいと思います。

map[string]map[string]string

二つのmapのキーにstring型を指定した例です。
ネストしたmapは、データをグループ化したい場合に便利です。

1
2
3
4
5
6
7
8
9
catDogMap := make(map[string]map[string]string)
catDogMap["猫"] = make(map[string]string)
catDogMap["犬"] = make(map[string]string)
catDogMap["猫"]["みけ"] = "みけ猫"
catDogMap["猫"]["ぶち"] = "ぶち猫"
catDogMap["犬"]["柴"] = "柴犬"
catDogMap["犬"]["秋田"] = "秋田犬"

fmt.Println(catDogMap)
実行結果
1
map[犬:map[柴:柴犬 秋田:秋田犬] 猫:map[ぶち:ぶち猫 みけ:みけ猫]]

一つ目のマップに"犬"と"猫"でグループ化しました。
これでcatDogMap[“猫”]とすれば猫のデータだけを取得できます。便利ですね😉

mapがいくつも連なる時はそれぞれのmapを使用する前にmakeする必要があります。
ここらへんがインタプリタ型言語の連想配列と違う所ですね。

catDogMap[“猫”]の型はmap[string]stringですから、catDogMap[“猫”]に値を代入する前にmake(map[string]string)を実行しmapを作成する必要があります。

make関数を実行せずに代入しようとすると、assignment to entry in nil mapエラーとなります。

1
2
3
4
5
6
7
8
9
catDogMap := make(map[string]map[string]string)
//catDogMap["猫"] = make(map[string]string)
catDogMap["犬"] = make(map[string]string)
catDogMap["猫"]["みけ"] = "みけ猫" //←ここでassignment to entry in nil mapエラー!!!
catDogMap["猫"]["ぶち"] = "ぶち猫"
catDogMap["犬"]["柴"] = "柴犬"
catDogMap["犬"]["秋田"] = "秋田犬"

fmt.Println(catDogMap)

割とやりがちなので注意しましょう。

1次元配列からネストしたmapをループ処理で作成

先ほどの例では直書きでデータを設定しました。
ですが実際には、1次元配列からネストしたmapをループ処理で作成する場面が多いと思います。
猫と犬の1次元配列から、先ほどと同一のネストしたmapをループ処理で作成する例を下記に示します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
type Animal struct {
	AnimalKind string
	Kind       string
	Name       string
}
animals := []Animal{
	{
		AnimalKind: "猫",
		Kind:       "みけ",
		Name:       "みけ猫",
	},
	{
		AnimalKind: "猫",
		Kind:       "ぶち",
		Name:       "ぶち猫",
	},
	{
		AnimalKind: "犬",
		Kind:       "柴",
		Name:       "柴犬",
	},
	{
		AnimalKind: "犬",
		Kind:       "秋田",
		Name:       "秋田犬",
	},
}
animalMap := make(map[string]map[string]string)
for _, animal := range animals {
	_, ok := animalMap[animal.AnimalKind]
	if !ok {
		animalMap[animal.AnimalKind] = make(map[string]string)
	}
	animalMap[animal.AnimalKind][animal.Kind] = animal.Name
}

fmt.Println(animalMap)
実行結果
1
map[犬:map[柴:柴犬 秋田:秋田犬] 猫:map[ぶち:ぶち猫 みけ:みけ猫]]

ネストしたmapをループ処理で作成できました。

ポイントは31行目でキーが存在するかチェックしている所です。
キーが存在しなければmap[string]stringは未作成なのでmake関数で作成してあげます。

mapにキーを指定して値を取得するのに二つの戻り値があって驚いたかもしれませんが、mapから値を取得するときに実際は戻り値が二つ返却されていて、1番目は値で、2番目の戻り値がbool型になっています。
2番目の戻り値はキーが存在すればtrue、存在しなければfalseが返却されます。
2番目の戻り値は省略可能なので必要なければ値だけを取得しても問題ありません。

golangでmapを使い始めの時はこの形が中々想像できず悩む方が多いと思いますが、理解してしまえば簡単に実装できるようになります。

mapのキーに指定できる型と指定できない型

ここまで色々な型をmapのキーに指定してきたので、もはや何でも指定できそうですが、そうもいきません。

mapのキーに指定できるものは== 演算子で比較可能な下記の型に限ります。

ブール型、数値型、文字列型、ポインタ型、チャネル型、インターフェイス型、およびこれらの型のみを含む構造体または配列です。
引用元:Go maps in action - Key types

mapのキーに指定できないものは下記の型になります。

スライス、マップ、および関数
引用元:Go maps in action - Key types

構造体のメンバ変数にmapのキーとして指定できない型が含まれているとinvalid map key type 〇〇エラーとなります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
type Cat struct {
	Id int
}

type CatError struct {
	Id int
	AtributteMap map[int]string
}
//OK
m := make(map[Cat]string)
m[Cat{}] = "みけ猫"

//NG:Catのメンバ変数にmap型が入っているためキーにできない(invalid map key type CatError)
//m2 := make(map[CatError]string)
//m2[CatError{}] = "みけ猫"

構造体をmapのキーにしようと思ったときに、スライス、マップ、関数のいずれかがメンバ変数にあるとキーに指定できなくなるため、構造体のデータ設計は慎重に行いましょう。

2022/4/2追記
mapのキーに指定できるデータ型の集合を定義したインターフェースcopmparableがGo 1.18で追加されました。
詳しくはGo 1.18のジェネリクスを使ってみる - comparableについて の記事をお読みください。

mapでループすると毎回順番が違う

これはインタプリタ型言語からきた方は発狂すると思いますが😅
golangではmapでループすると毎回違った順番でデータが取り出されます。

1
2
3
for _,cat := range catMap {
	fmt.Println(cat)
}
実行結果
1
2
3
4
キジ猫
ぶち猫
柴犬
みけ猫

何回か実行すると最初に代入した順序と違った出力結果になります。

これは不具合ではなく意図的にランダムになるように実装されています。
理由は、mapの反復順序が定義されておらず、ハードウェアプラットフォーム間で、ある環境ではテストに合格して別の環境では失敗する可能性があったためらしいです。
map内部の実装上の問題なので詳細は説明できないですが、要するに依存されると困るから予め順序はぐちゃぐちゃにしてるよ、ということです😂

ですのでmapでループする場合は、キーのリストを取得した後、キーを並べ替えてループするという手順が必要になります。(めんどくさい🙃)

今回の例でキーを並べ替えてループするコードを下記に示します。

1
2
3
4
5
6
7
8
9
// キーを並べ替えてループする
var keys []int
for k := range catMap {
	keys = append(keys, k)
}
sort.Ints(keys)
for _,k := range keys {
	fmt.Println(catMap[k])
}
実行結果
1
2
3
4
みけ猫
キジ猫
ぶち猫
柴犬

上記のキーを取得して並べ替えるコードは公式のコードを今回の例で置き換えたものです。
Go maps in action - Iteration order

単純なmapならまだマシですが、ネストしたmapですと、取り出したマップに大してまたキーのリストを取得して並べ替えてループするといった手順になるため、それなりにコードが膨らみます。
うまい書き方があればいいのですが、どうもいい方法が見つからなかったので私はその都度書いてます😑
関数化してもmapのキーの型が違うと型毎に関数を書く必要がありますし、ソートの条件もケースバイケースでよく変わるのでコードの共通化が難しいんですよね。
また共通化するほどコードが多いわけでもないですし、ここらへんのコードは毎回モヤっとした気持ちになりながら書いてます😅

golangがいまいち流行らない原因の一つかも知れないですね…。
こういう時にPHPは書きやすかったなぁとしみじみ思います。

mapでキーを削除する

mapでキーを削除するコードを下記に示します。
最初に作成した猫mapで"柴犬"が紛れ込んでいたので削除しちゃいましょう。

1
2
delete(catMap, 3)
fmt.Println(catMap)
実行結果
1
map[0:みけ猫 1:キジ猫 2:ぶち猫]

キーの削除は組み込み関数のdeleteで行います。
第1引数に削除元のmapを指定します。
第2引数に削除したいキーを指定します。
catMapのキーはint型でしたので今回は 3 を指定しています。

delete関数に関しては特に躓くところはないと思います。
ちなみに存在しないキーを削除しても何も起こりません。

まとめ

私がmapを使って躓いたところを書いてみましたが、いかがでしたか。
mapはよく使うのでもう少し楽に書けたらいいのですが、そこは今後のアップデートで改善を期待したい所です。

今後もmapを使って気になる所があれば随時更新していきたいと思います。

プログラム全体

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package main

import (
	"fmt"
	"runtime"
	"sort"
	"time"
)

func main() {
	//----------------------------------------------------------------------------
	// map[int]string
	//----------------------------------------------------------------------------
	catMap := make(map[int]string)
	catMap[0] = "みけ猫"
	catMap[1] = "キジ猫"
	catMap[2] = "ぶち猫"
	catMap[3] = "柴犬"

	fmt.Println("----------map[int]string----------")
	fmt.Println(catMap)
	//----------------------------------------------------------------------------
	// map[time.Time]string
	//----------------------------------------------------------------------------
	Jst, err := time.LoadLocation("Asia/Tokyo")
	if err != nil {
		panic(err)
	}
	catDay, err := time.ParseInLocation("2006-01-02", "2022-02-22", Jst)
	if err != nil {
		panic(err)
	}
	emperorsBirthday, err := time.ParseInLocation("2006-01-02", "2022-02-23", Jst)
	if err != nil {
		panic(err)
	}
	springDay, err := time.ParseInLocation("2006-01-02", "2022-03-21", Jst)
	if err != nil {
		panic(err)
	}
	springDayUtc, err := time.ParseInLocation("2006-01-02", "2022-03-21", time.UTC)
	if err != nil {
		panic(err)
	}
	anniversaryDaysMap := make(map[time.Time]string)
	anniversaryDaysMap[catDay] = "猫の日"
	anniversaryDaysMap[emperorsBirthday] = "天皇誕生日"
	anniversaryDaysMap[springDay] = "春分の日"
	anniversaryDaysMap[springDayUtc] = "春分の日(UTC)"

	fmt.Println("----------map[time.Time]string----------")
	fmt.Println(anniversaryDaysMap)
	//----------------------------------------------------------------------------
	// map[interface{}]string
	//----------------------------------------------------------------------------
	mixCatMap := make(map[interface{}]string)
	mixCatMap[0] = "みけ猫"
	mixCatMap["kiji"] = "キジ猫"
	mixCatMap[catDay] = "ねこの日"

	fmt.Println("----------map[interface{}]string----------")
	fmt.Println(mixCatMap)
	//----------------------------------------------------------------------------
	// map[*int]string
	//----------------------------------------------------------------------------
	catPointerMap := make(map[*int]string)
	mike := 0
	kiji := 1
	buti := 2
	catPointerMap[&mike] = "みけ猫"
	catPointerMap[&kiji] = "キジ猫"
	catPointerMap[&buti] = "ぶち猫"

	fmt.Println("----------map[*int]string----------")
	fmt.Println(catPointerMap)
	//----------------------------------------------------------------------------
	// map[string]map[string]string
	//----------------------------------------------------------------------------
	catDogMap := make(map[string]map[string]string)
	catDogMap["猫"] = make(map[string]string)
	catDogMap["犬"] = make(map[string]string)
	catDogMap["猫"]["みけ"] = "みけ猫"
	catDogMap["猫"]["ぶち"] = "ぶち猫"
	catDogMap["犬"]["柴"] = "柴犬"
	catDogMap["犬"]["秋田"] = "秋田犬"

	fmt.Println("----------map[string]map[string]string----------")
	fmt.Println(catDogMap)
	//----------------------------------------------------------------------------
	// mapインmapでデータをグループ化
	//----------------------------------------------------------------------------
	type Animal struct {
		AnimalKind string
		Kind       string
		Name       string
	}
	animals := []Animal{
		{
			AnimalKind: "猫",
			Kind:       "みけ",
			Name:       "みけ猫",
		},
		{
			AnimalKind: "猫",
			Kind:       "ぶち",
			Name:       "ぶち猫",
		},
		{
			AnimalKind: "犬",
			Kind:       "柴",
			Name:       "柴犬",
		},
		{
			AnimalKind: "犬",
			Kind:       "秋田",
			Name:       "秋田犬",
		},
	}
	animalMap := make(map[string]map[string]string)
	for _, animal := range animals {
		_, ok := animalMap[animal.AnimalKind]
		if !ok {
			animalMap[animal.AnimalKind] = make(map[string]string)
		}
		animalMap[animal.AnimalKind][animal.Kind] = animal.Name
	}

	fmt.Println("----------mapインmapでデータをグループ化----------")
	fmt.Println(animalMap)
	//----------------------------------------------------------------------------
	// mapのキーに指定できる型と指定できない型
	//----------------------------------------------------------------------------
	type Cat struct {
		Id int
	}
	type CatError struct {
		Id           int
		AtributteMap map[int]string
	}
	//OK
	m := make(map[Cat]string)
	m[Cat{}] = "みけ猫"

	//NG:Catのメンバ変数にmap型が入っているためキーにできない(invalid map key type Catエラー)
	//m2 := make(map[CatError]string)
	//m2[CatError{}] = "みけ猫"
	//----------------------------------------------------------------------------
	// mapでループすると毎回順番が違う
	//----------------------------------------------------------------------------
	fmt.Println("----------mapでループすると毎回順番が違う----------")
	fmt.Println("--mapでループ")
	for _, cat := range catMap {
		fmt.Println(cat)
	}
	// キーを並べ替えてループする
	var keys []int
	for k := range catMap {
		keys = append(keys, k)
	}
	sort.Ints(keys)
	fmt.Println("--ソートしたキーでループ")
	for _, k := range keys {
		fmt.Println(catMap[k])
	}
	//----------------------------------------------------------------------------
	// mapでキーを削除する
	//----------------------------------------------------------------------------
	delete(catMap, 3)

	fmt.Println("----------mapでキーを削除する----------")
	fmt.Println(catMap)

	fmt.Printf("\nGo version: %s\n", runtime.Version())
}

実行結果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
----------map[int]string----------
map[0:みけ猫 1:キジ猫 2:ぶち猫 3:柴犬]
----------map[time.Time]string----------
map[2022-02-22 00:00:00 +0900 JST:猫の日 2022-02-23 00:00:00 +0900 JST:天皇誕生日 2022-03-21 00:00:00 +0900 JST:春分の日 2022-03-21 00:00:00 +0000 UTC:春分の日(UTC)]
----------map[interface{}]string----------
map[0:みけ猫 kiji:キジ猫 2022-02-22 00:00:00 +0900 JST:ねこの日]
----------map[*int]string----------
map[0xc000114038:みけ猫 0xc000114040:キジ猫 0xc000114048:ぶち猫]
----------map[string]map[string]string----------
map[犬:map[柴:柴犬 秋田:秋田犬] 猫:map[ぶち:ぶち猫 みけ:みけ猫]]
----------mapインmapでデータをグループ化----------
map[犬:map[柴:柴犬 秋田:秋田犬] 猫:map[ぶち:ぶち猫 みけ:みけ猫]]
----------mapでループすると毎回順番が違う----------
--mapでループ
柴犬
みけ猫
キジ猫
ぶち猫
--ソートしたキーでループ
みけ猫
キジ猫
ぶち猫
柴犬
----------mapでキーを削除する----------
map[0:みけ猫 1:キジ猫 2:ぶち猫]

Go version: go1.17.8

実行環境

1
2
Go Playgroundで実行
Go version go1.17.8

スポンサーリンク

共有

もふもふ
著者
もふもふ
プログラマ。汎用系→ゲームエンジニア→Webエンジニア