Go言語(Golang)でプログラムを書いていると、「名前と点数を組み合わせたい」「単語の出現回数を数えたい」など、データをキーと値のペアで管理したい場面がよくあります。
「スライスとなにが違うの?」
「どうやって使えばいいの?」
そんな疑問をかかえていませんか?
そんなときに便利なのがmap(マップ)です。
mapは、他の言語でいう辞書型や連想配列に相当し、データの検索や分類、頻度の計算などにとても便利です。
この記事では、Goのmapについて、定義・追加・更新・削除といった基本操作から、実用的な使い方まで、初心者にもわかりやすく解説します。
mapってなに?|データを「鍵と宝箱」で管理する便利な仕組み
mapの基本的な考え方
mapは「キー(鍵)と値(データ)」のペアを保存するデータ構造です。
辞書で単語(キー)を引いて意味(値)を調べるのと同じような仕組みです。
身近な例で理解しよう
学生の成績管理
田中さん → 85点
佐藤さん → 92点
山田さん → 78点
商品の在庫管理
りんご → 50個
ばなな → 30個
みかん → 25個
mapが得意なこと
用途 | 具体例 | メリット |
---|---|---|
データの分類 | カテゴリごとに商品をまとめる | 整理しやすい |
頻度のカウント | 文字や単語の出現回数を数える | 自動で集計 |
IDと情報の紐づけ | 学籍番号と学生情報を関連付け | 高速で検索 |
スライスとの違い
特徴 | スライス | map |
---|---|---|
データの管理方法 | 順番(インデックス)で管理 | キー(名前)で管理 |
アクセス方法 | slice[0] , slice[1] | map["田中"] , map["佐藤"] |
データの順番 | 順番が決まっている | 順番は保証されない |
適している用途 | リスト、配列的なデータ | 関連付けられたデータ |
mapの役割は「関連付けられたデータの効率的な管理」です。次は、mapの定義方法と初期化の書き方を見てみましょう。
mapの作り方|定義から初期化まで3つの方法
基本的な宣言方法
var m map[string]int
この書き方では、まだ使える状態になっていません。
使おうとするとエラー(panic)になってしまいます。
方法1:make関数を使う初期化
基本の書き方
m := make(map[string]int)
実際の使用例
package main
import "fmt"
func main() {
// 空のmapを作る
scores := make(map[string]int)
// データを追加
scores["田中"] = 85
scores["佐藤"] = 92
scores["山田"] = 78
fmt.Println(scores) // map[佐藤:92 田中:85 山田:78]
}
方法2:リテラル(初期値付き)で初期化
基本の書き方
m := map[string]string{
"apple": "りんご",
"banana": "ばなな",
"orange": "みかん",
}
実際の使用例
package main
import "fmt"
func main() {
// 最初からデータを入れて作る
fruits := map[string]string{
"apple": "りんご",
"banana": "ばなな",
"orange": "みかん",
}
fmt.Println(fruits["apple"]) // りんご
}
方法3:空のmapリテラル
基本の書き方
m := map[string]int{}
これはmake(map[string]int)
と同じ効果があります。
よく使うキーと値の型の組み合わせ
キーの型 | 値の型 | 使用例 |
---|---|---|
string | int | 名前と点数、単語と出現回数 |
string | string | 英語と日本語、IDと名前 |
int | string | 番号と名前、IDと情報 |
string | []string | カテゴリと商品リスト |
string | bool | 名前と合格/不合格 |
makeやリテラルで初期化することで、mapをすぐに使える状態にできます。次は、mapへのデータ追加・更新・取得方法を見てみましょう。
mapの基本操作|データの追加・更新・取得をマスターしよう
データの追加と更新
基本の書き方
m["キー"] = 値
実際の例
package main
import "fmt"
func main() {
scores := make(map[string]int)
// データを追加
scores["田中"] = 85
scores["佐藤"] = 92
scores["山田"] = 78
// データを更新(同じキーに新しい値)
scores["田中"] = 90
fmt.Println(scores) // map[佐藤:92 田中:90 山田:78]
}
データの取得
基本の取得方法
値 := m["キー"]
実際の例
package main
import "fmt"
func main() {
fruits := map[string]string{
"apple": "りんご",
"banana": "ばなな",
}
// 値を取得
appleName := fruits["apple"]
fmt.Println(appleName) // りんご
// 存在しないキーを取得すると「ゼロ値」が返る
orangeName := fruits["orange"]
fmt.Println(orangeName) // (空文字)
}
キーの存在確認(重要!)
基本の書き方
値, 存在するか := m["キー"]
実際の例
package main
import "fmt"
func main() {
scores := map[string]int{
"田中": 85,
"佐藤": 92,
}
// キーが存在する場合
score, ok := scores["田中"]
if ok {
fmt.Printf("田中さんの点数は %d 点です\n", score)
} else {
fmt.Println("田中さんのデータがありません")
}
// キーが存在しない場合
score, ok = scores["山田"]
if ok {
fmt.Printf("山田さんの点数は %d 点です\n", score)
} else {
fmt.Println("山田さんのデータがありません")
}
}
出力結果
田中さんの点数は 85 点です
山田さんのデータがありません
よく使うパターンの例
パターン1:存在チェック付きの処理
if score, exists := scores["田中"]; exists {
fmt.Printf("田中さん: %d点\n", score)
} else {
fmt.Println("田中さんのデータが見つかりません")
}
パターン2:デフォルト値を設定
score := scores["田中"]
if score == 0 {
score = 50 // デフォルト値
}
fmt.Printf("点数: %d\n", score)
mapは、存在チェックもかんたんにできるため、予期しないエラーを防げます。次は、要素の削除やループ処理を見てみましょう。
データの削除とループ処理|mapの中身を自由に操作しよう
データの削除
基本の書き方
delete(map名, "削除したいキー")
実際の例
package main
import "fmt"
func main() {
fruits := map[string]string{
"apple": "りんご",
"banana": "ばなな",
"orange": "みかん",
}
fmt.Println("削除前:", fruits)
// "banana"を削除
delete(fruits, "banana")
fmt.Println("削除後:", fruits)
// 存在しないキーを削除してもエラーにならない
delete(fruits, "grape") // エラーなし
}
出力結果
削除前: map[apple:りんご banana:ばなな orange:みかん]
削除後: map[apple:りんご orange:みかん]
すべてのデータを見るループ処理
基本の書き方
for キー, 値 := range map名 {
// 処理
}
実際の例
package main
import "fmt"
func main() {
scores := map[string]int{
"田中": 85,
"佐藤": 92,
"山田": 78,
"鈴木": 88,
}
// すべての成績を表示
fmt.Println("=== 成績一覧 ===")
for name, score := range scores {
fmt.Printf("%s: %d点\n", name, score)
}
}
出力結果の例
=== 成績一覧 ===
田中: 85点
佐藤: 92点
山田: 78点
鈴木: 88点
キーだけ、値だけを取得する方法
キーだけ取得
for key := range scores {
fmt.Println("名前:", key)
}
値だけ取得
for _, score := range scores {
fmt.Println("点数:", score)
}
実用的なループの例
例1:合格者を調べる
package main
import "fmt"
func main() {
scores := map[string]int{
"田中": 85,
"佐藤": 92,
"山田": 78,
"鈴木": 88,
}
fmt.Println("=== 合格者一覧(80点以上)===")
for name, score := range scores {
if score >= 80 {
fmt.Printf("%s: %d点 ✓\n", name, score)
}
}
}
例2:平均点を計算
package main
import "fmt"
func main() {
scores := map[string]int{
"田中": 85,
"佐藤": 92,
"山田": 78,
"鈴木": 88,
}
total := 0
count := 0
for _, score := range scores {
total += score
count++
}
average := float64(total) / float64(count)
fmt.Printf("平均点: %.1f点\n", average)
}
deleteやrangeを使えば、mapの中身を自在に操作できます。
次は、mapの応用テクニックや注意点を見てみましょう。
実用的な活用テクニック|mapでできる便利な処理
応用例1:文字の出現回数をカウント
何をするプログラム? 文字列の中で、それぞれの文字が何回出てくるかを数えます。
package main
import "fmt"
func main() {
text := "gopher"
// 文字ごとの出現回数を記録するmap
charCount := make(map[rune]int)
// 文字列を1文字ずつチェック
for _, char := range text {
charCount[char]++ // その文字の回数を1増やす
}
// 結果を表示
fmt.Println("=== 文字の出現回数 ===")
for char, count := range charCount {
fmt.Printf("'%c': %d回\n", char, count)
}
}
出力結果
=== 文字の出現回数 ===
'g': 1回
'o': 1回
'p': 1回
'h': 1回
'e': 1回
'r': 1回
応用例2:グループ分けして集計
何をするプログラム? 学生を学年ごとに分けて、それぞれの学年に何人いるかを数えます。
package main
import "fmt"
func main() {
// 学生のデータ(名前と学年)
students := map[string]int{
"田中": 1,
"佐藤": 2,
"山田": 1,
"鈴木": 3,
"高橋": 2,
"渡辺": 1,
}
// 学年ごとの人数を記録するmap
gradeCount := make(map[int]int)
// 各学生の学年を確認して人数を数える
for _, grade := range students {
gradeCount[grade]++
}
// 結果を表示
fmt.Println("=== 学年別人数 ===")
for grade, count := range gradeCount {
fmt.Printf("%d年生: %d人\n", grade, count)
}
}
出力結果
=== 学年別人数 ===
1年生: 3人
2年生: 2人
3年生: 1人
応用例3:データの変換・マッピング
何をするプログラム? 英語の月名を日本語に変換します。
package main
import "fmt"
func main() {
// 英語から日本語への変換表
monthMap := map[string]string{
"January": "1月",
"February": "2月",
"March": "3月",
"April": "4月",
"May": "5月",
"June": "6月",
"July": "7月",
"August": "8月",
"September": "9月",
"October": "10月",
"November": "11月",
"December": "12月",
}
englishMonths := []string{"March", "June", "September"}
fmt.Println("=== 月名の変換 ===")
for _, month := range englishMonths {
if japanese, exists := monthMap[month]; exists {
fmt.Printf("%s → %s\n", month, japanese)
} else {
fmt.Printf("%s → 変換できません\n", month)
}
}
}
応用例4:複雑なデータ構造(mapの中にスライス)
何をするプログラム? 各チームのメンバー一覧を管理します。
package main
import "fmt"
func main() {
// チーム名をキーにして、メンバーのスライスを値にする
teams := map[string][]string{
"開発チーム": {"田中", "佐藤", "山田"},
"営業チーム": {"鈴木", "高橋"},
"企画チーム": {"渡辺", "伊藤", "加藤", "小林"},
}
fmt.Println("=== チーム構成 ===")
for teamName, members := range teams {
fmt.Printf("\n【%s】(%d人)\n", teamName, len(members))
for i, member := range members {
fmt.Printf(" %d. %s\n", i+1, member)
}
}
}
出力結果
=== チーム構成 ===
【開発チーム】(3人)
1. 田中
2. 佐藤
3. 山田
【営業チーム】(2人)
1. 鈴木
2. 高橋
【企画チーム】(4人)
1. 渡辺
2. 伊藤
3. 加藤
4. 小林
これらの例を見ると、mapがとても柔軟で強力なデータ構造であることがわかります。
次は、mapを使うときの注意点を確認しましょう。
mapを使うときの注意点|知っておきたい重要なポイント
注意点1:キーに使える型の制限
使えるキーの型
- 基本型:
int
,string
,bool
,float64
など - 配列:
[3]int
など(要素数が決まっているもの) - 構造体:比較可能なフィールドのみ
使えないキーの型
- スライス:
[]int
,[]string
など - map:
map[string]int
など - 関数:
func()
など
// ❌ これはエラーになる
// var badMap map[[]string]int
// ✅ これは正しい
var goodMap map[string]int
var arrayKeyMap map[[3]int]string // 配列はOK
注意点2:mapの初期化を忘れずに
ダメな例(panic になる)
var m map[string]int
m["test"] = 1 // panic: assignment to entry in nil map
正しい例
m := make(map[string]int)
m["test"] = 1 // OK
注意点3:mapのコピーは「浅いコピー」
package main
import "fmt"
func main() {
original := map[string]int{
"apple": 1,
"banana": 2,
}
// これは「浅いコピー」- 同じデータを指している
copied := original
// copiedを変更すると、originalも変わる
copied["orange"] = 3
fmt.Println("original:", original) // map[apple:1 banana:2 orange:3]
fmt.Println("copied:", copied) // map[apple:1 banana:2 orange:3]
}
深いコピーをしたい場合
func deepCopyMap(original map[string]int) map[string]int {
copy := make(map[string]int)
for key, value := range original {
copy[key] = value
}
return copy
}
注意点4:mapの要素は順序が保証されない
package main
import "fmt"
func main() {
m := map[string]int{
"a": 1,
"b": 2,
"c": 3,
}
// 実行するたびに順番が変わる可能性がある
for key, value := range m {
fmt.Printf("%s: %d\n", key, value)
}
}
順序が必要な場合の解決方法
package main
import (
"fmt"
"sort"
)
func main() {
m := map[string]int{
"banana": 2,
"apple": 1,
"cherry": 3,
}
// キーを取得してソート
keys := make([]string, 0, len(m))
for key := range m {
keys = append(keys, key)
}
sort.Strings(keys)
// ソートされた順序で表示
for _, key := range keys {
fmt.Printf("%s: %d\n", key, m[key])
}
}
注意点5:mapのゼロ値とnil
package main
import "fmt"
func main() {
var m map[string]int
// 値の読み取りは可能(ゼロ値が返る)
value := m["test"]
fmt.Println(value) // 0
// 存在チェックも可能
_, exists := m["test"]
fmt.Println(exists) // false
// でも書き込みはpanic
// m["test"] = 1 // panic!
}
よく使うmapのパターン集|実際の開発で役立つ書き方
パターン1:設定値の管理
package main
import "fmt"
func main() {
config := map[string]interface{}{
"port": 8080,
"host": "localhost",
"debug": true,
"maxUsers": 1000,
"appName": "MyApp",
}
// 型アサーションで使用
if port, ok := config["port"].(int); ok {
fmt.Printf("サーバーポート: %d\n", port)
}
if appName, ok := config["appName"].(string); ok {
fmt.Printf("アプリ名: %s\n", appName)
}
}
パターン2:キャッシュの実装
package main
import "fmt"
// 簡単なキャッシュの例
var cache = make(map[string]string)
func expensiveOperation(input string) string {
// キャッシュをチェック
if result, exists := cache[input]; exists {
fmt.Printf("キャッシュから取得: %s\n", input)
return result
}
// 重い処理をシミュレート
fmt.Printf("計算中: %s\n", input)
result := "処理済み_" + input
// 結果をキャッシュに保存
cache[input] = result
return result
}
func main() {
// 最初の呼び出し(計算する)
fmt.Println(expensiveOperation("データA"))
// 2回目の呼び出し(キャッシュから取得)
fmt.Println(expensiveOperation("データA"))
// 別のデータ(計算する)
fmt.Println(expensiveOperation("データB"))
}
パターン3:エラーコードと説明の管理
package main
import "fmt"
func main() {
errorMessages := map[int]string{
404: "ページが見つかりません",
500: "サーバーエラーが発生しました",
403: "アクセスが禁止されています",
401: "認証が必要です",
}
errorCodes := []int{200, 404, 500, 403}
for _, code := range errorCodes {
if message, exists := errorMessages[code]; exists {
fmt.Printf("エラー %d: %s\n", code, message)
} else {
fmt.Printf("エラー %d: 不明なエラーです\n", code)
}
}
}
パターン4:グルーピングとフィルタリング
package main
import "fmt"
type Person struct {
Name string
Age int
City string
}
func main() {
people := []Person{
{"田中", 25, "東京"},
{"佐藤", 30, "大阪"},
{"山田", 22, "東京"},
{"鈴木", 28, "名古屋"},
{"高橋", 35, "大阪"},
}
// 都市ごとにグループ化
cityGroups := make(map[string][]Person)
for _, person := range people {
cityGroups[person.City] = append(cityGroups[person.City], person)
}
// 結果を表示
for city, group := range cityGroups {
fmt.Printf("\n=== %s在住 (%d人) ===\n", city, len(group))
for _, person := range group {
fmt.Printf(" %s (%d歳)\n", person.Name, person.Age)
}
}
}
まとめ|mapをマスターしてGo言語のデータ処理を効率化しよう
覚えておきたい重要なポイント
mapの基本
- 定義と初期化:
make(map[string]int)
またはmap[string]int{}
- データ操作:追加・更新・取得・削除の基本操作
- 存在チェック:
value, ok := map[key]
パターンの活用 - ループ処理:
for key, value := range map
での全要素処理
実用的な活用方法
- 頻度のカウント(文字数、単語数など)
- データの分類とグループ化
- 設定値やエラーメッセージの管理
- キャッシュの実装
注意すべきポイント
- 初期化を忘れない(panic防止)
- キーの型制限を理解する
- 順序が保証されないことを理解する
- 浅いコピーの性質を理解する
よくある質問(FAQ)
Q: mapとスライスはどう使い分けるべきですか?
A: 順序が重要で連続したデータはスライス、キーで検索したいデータはmapを使いましょう。
Q: mapのキーに構造体は使えますか?
A: 比較可能なフィールドのみの構造体なら使えます。スライスやmapをフィールドに持つ構造体は使えません。
Q: mapの要素数を調べるにはどうすればいいですか?
A: len(map名)
で要素数を取得できます。
Q: mapをからっぽにするにはどうすればいいですか?
A: 新しいmapを作り直すか、rangeで全要素をdeleteします。
コメント