1. Home
  2. golang
  3. for文を減らして読みやすく。Goのmapsパッケージを使ったmap操作の整理術

for文を減らして読みやすく。Goのmapsパッケージを使ったmap操作の整理術

  • 公開日
  • カテゴリ:golang
  • タグ:golang
for文を減らして読みやすく。Goのmapsパッケージを使ったmap操作の整理術

Go の map 型を扱うとき、キーだけを取り出したり、値だけを集めたり、2 つの map を比較したりと、さまざまな操作が必要になる場面があります。

そんなときに役立つのが、標準ライブラリの maps パッケージです。

本記事では、map 操作にありがちな for ループの繰り返しを減らし、より簡潔で読みやすいコードを書くための maps パッケージの活用方法を紹介します。

contents

  1. maps パッケージ
  2. maps パッケージで使用可能な関数
  3. All: map のすべての要素をイテレータとして取得する
  4. Clone: map をまるごと複製する
  5. Collect: イテレータから map を構築する
  6. Copy: map の内容を別の map にコピーする
  7. DeleteFunc: 条件に一致する要素を削除する
  8. Equal: 2つの map が等しいかを比較する
  9. EqualFunc: カスタム条件で 2 つの map を比較する
  10. Insert: イテレータから map に要素を追加する
  11. Keys: map のすべてのキーを取得する
  12. Values: map のすべての値を取得する

maps パッケージ

maps は、Go の組み込み map 型(キーと値のペアで構成される連想配列)を、より便利に扱うためのユーティリティパッケージです。

このパッケージを使うと、map の複製(Clone)、比較(Equal)、要素の抽出(Keys・Values)、条件付き削除(DeleteFunc)など、日常的によく行う操作を簡潔かつ安全に記述できます。

イメージとしては、map を扱う処理を for 文で 1 つ 1 つ書いていた場面でも、maps パッケージを使えば、その処理を 1 行で明示的に記述できるようになる。そんな役割を果たしてくれるパッケージです。

maps パッケージで使用可能な関数

maps パッケージには、map をより柔軟かつ簡潔に扱うための便利な関数が数多く用意されています。どの関数も汎用的で、型に依存せずに使える点が特徴です。

関数名説明
Allmap のすべての要素をイテレータとして取得できる。主に Insert や Collect の元データに利用される。
Clone指定した map の内容をまるごと複製して新しい map を作成する。
Collectイテレータから map を構築する。slices.All などと組み合わせて使う。
Copy2つの map 間で要素をコピーする。上書きされる点に注意。
DeleteFunc条件に一致した map の要素を削除する。関数で柔軟に条件を定義可能。
Equal2つの map が同じキーと値を持つかどうかを比較する。
EqualFunc値の比較にカスタム関数を使って、map の等価性を判定する。
Insertイテレータから map に要素を一括挿入する。map の結合や変換に便利。
Keysmap のすべてのキーをイテレータとして取得する。順序は保証されない。
Valuesmap のすべての値をイテレータとして取得する。順序は保証されない。

All: map のすべての要素をイテレータとして取得する

maps.All は、指定した map のすべてのキーと値を含むイテレータ(iter.Seq2[K, V])を生成する関数です。

このイテレータは、後続の maps.Insertmaps.Collect などで活用できるほか、単独でもループ処理に利用できます。

func All[Map ~map[K]V, K comparable, V any](m Map) iter.Seq2[K, V]

map の中身を (key, value) のペアとして順に処理したい場合、for ループで map を直接回すのが手段の一つですが、maps.All を使えば map をイテレータとして扱えるようになり、処理の組み立てが柔軟になります。

fruits := map[string]int{
    "apple":  5,
    "banana": 10,
    "orange": 7,
}

//
// maps を使わない場合
//
result := make([]string, 0, len(fruits))
for k, v := range fruits {
    result = append(result, fmt.Sprintf("%s=%d", k, v))
}
fmt.Println(result)
// => [apple=5 banana=10 orange=7]


//
//  maps.All を使う場合
//
result = make([]string, 0, len(fruits))
for k, v := range maps.All(fruits) {
    result = append(result, fmt.Sprintf("%s=%d", k, v))
}
fmt.Println(result)
// => [apple=5 banana=10 orange=7]

手動の場合は for k, v := range で都度処理を記述しますが、maps.All を使えば map を汎用イテレータとして扱えるため、他の処理関数(Insert, Collect など)にも渡せるようになります。

単体で使うだけでは冗長に見えるかもしれませんが、他の関数に渡せる再利用可能な処理単位として map を扱える点が maps.All の強みです。

Clone: map をまるごと複製する

maps.Clone は、指定した map のキーと値をすべてコピーし、新しい map を返す関数です。

元の map を変更せずにその内容を複製したい場合に便利で、副作用のない処理を行いたいときに特に有効です。

func Clone[M ~map[K]V, K comparable, V any](m M) M

以下は、map を複製して新しい map を作成する例です。

fruits := map[string]int{
    "apple":  5,
    "banana": 10,
    "orange": 7,
}

//
// maps を使わない場合
//
clone := make(map[string]int)
for k, v := range fruits {
    clone[k] = v
}
fmt.Println(clone)
// => map[apple:5 banana:10 orange:7]

//
// maps.Clone を使う場合
//
clone = maps.Clone(fruits)
fmt.Println(clone)
// => map[apple:5 banana:10 orange:7]

手動でコピーする場合は makefor ループが必要になりますが、maps.Clone を使えば 1 行で完結します。

コードの可読性が高まり、ミスも減らせるため、map の内容をそのまま複製したいだけのケースでは積極的に使いたい関数です。

Collect: イテレータから map を構築する

maps.Collect は、iter.Seq2[K, V] 型のイテレータから map を生成する関数です。

スライスなどから map を構築したいときに、インデックスや key を意識して手動で map を組み立てる代わりに、この関数を使うことで簡潔に変換できます。

func Collect[K comparable, V any](seq iter.Seq2[K, V]) map[K]V

以下は、スライスから "インデックス → 要素" の map を作成する例です。

fruits := []string{"apple", "banana", "orange"}

//
// maps を使わない場合
//
manual := make(map[int]string)
for i, fruit := range fruits {
    manual[i] = fruit
}
fmt.Println(manual)
// => map[0:apple 1:banana 2:orange]

//
// maps.Collect を使う場合
//
collected := maps.Collect(slices.All(fruits))
fmt.Println(collected)
// => map[0:apple 1:banana 2:orange]

手動で map を構築する場合は、ループ処理と代入を毎回書く必要がありますが、maps.Collect を使えば map の組み立てを抽象化して簡潔に記述できます。

特に、動的に生成されるデータや構造体のフィールドを map 化したいときに便利です。

Copy: map の内容を別の map にコピーする

maps.Copy は、ある map のすべての要素を別の map にコピーする関数です。

要素は上書きされるため、既存の map に追記する用途や、初期化済みの map にデータを流し込みたいときに使います。

func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2)

以下は、2つの map 間でデータをコピーする例です。

src := map[string]int{
    "apple":  5,
    "banana": 10,
    "orange": 7,
}

//
// maps を使わない場合
//
dst := make(map[string]int)
for k, v := range src {
    dst[k] = v
}
fmt.Println(dst)
// => map[apple:5 banana:10 orange:7]

//
// maps.Copy を使う場合
//
dst = make(map[string]int)
maps.Copy(dst, src)
fmt.Println(dst)
// => map[apple:5 banana:10 orange:7]

手動コピーでは for 文が必要ですが、maps.Copy を使えば 1 行で完了し、読みやすくミスも起きにくくなります。

既存の map を初期化せずに上書きしてしまうことがないよう、dst は事前に初期化しておく必要がある点に注意が必要です。

DeleteFunc: 条件に一致する要素を削除する

maps.DeleteFunc は、map の各要素に対して関数を適用し、条件を満たすものだけを削除する関数です。

フィルタ条件を関数として定義できるため、柔軟で再利用性の高い削除処理が可能になります。

func DeleteFunc[M ~map[K]V, K comparable, V any](m M, del func(K, V) bool)

以下は、値が 7 以上の要素を削除する例です。

fruits := map[string]int{
    "apple":  5,
    "banana": 10,
    "orange": 7,
    "melon":  3,
}

//
// maps を使わない場合
//
for k, v := range fruits {
    if v >= 7 {
        delete(fruits, k)
    }
}
fmt.Println(fruits)
// => map[apple:5 melon:3]

//
// maps.DeleteFunc を使う場合
//
fruits = map[string]int{
    "apple":  5,
    "banana": 10,
    "orange": 7,
    "melon":  3,
}
maps.DeleteFunc(fruits, func(k string, v int) bool {
    return v >= 7
})
fmt.Println(fruits)
// => map[apple:5 melon:3]

手動で条件付き削除を行うには if 文と delete を組み合わせる必要がありますが、maps.DeleteFunc を使えば条件を関数として切り出すことができ、読みやすく再利用しやすい構造になります。

Equal: 2つの map が等しいかを比較する

maps.Equal は、2つの map が同じキーと値を持っているかどうかを比較する関数です。

順序に依存せず、キーと値の一致を見て等価性を判定します。

func Equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2) bool

以下は、2つの map の等価性を比較する例です。

m1 := map[string]int{
    "apple":  5,
    "banana": 10,
}

m2 := map[string]int{
    "banana": 10,
    "apple":  5,
}

m3 := map[string]int{
    "apple":  5,
    "banana": 9,
}

//
// maps を使わない場合
//
equal := len(m1) == len(m2)
if equal {
    for k, v := range m1 {
        if m2[k] != v {
            equal = false
            break
        }
    }
}
fmt.Println(equal) // => true

//
// maps.Equal を使う場合
//
fmt.Println(maps.Equal(m1, m2)) // => true
fmt.Println(maps.Equal(m1, m3)) // => false

手動で等価性を比較するには map の長さを比較し、すべてのキーと値の一致を確認する必要があります。

maps.Equal を使えばこの処理を 1 行で書けるため、コードが簡潔かつ意図が明確になります。

EqualFunc: カスタム条件で 2 つの map を比較する

maps.EqualFunc は、2 つの map のキーが一致しているかを確認したうえで、対応する値を任意の比較関数で比較するための関数です。

値が構造体などの比較できない型でも、カスタム関数を用いて柔軟に等価性を判定できます。

func EqualFunc[M1 ~map[K]V1, M2 ~map[K]V2, K comparable, V1, V2 any](m1 M1, m2 M2, eq func(V1, V2) bool) bool

以下は、値が構造体である map を比較する例です。

type FruitStock struct {
    Count int
}

m1 := map[string]FruitStock{
    "apple":  {Count: 5},
    "banana": {Count: 10},
}

m2 := map[string]FruitStock{
    "banana": {Count: 10},
    "apple":  {Count: 5},
}

m3 := map[string]FruitStock{
    "apple":  {Count: 5},
    "banana": {Count: 9},
}

//
// maps を使わない場合
//
equal := len(m1) == len(m2)
if equal {
    for k, v := range m1 {
        if w, ok := m2[k]; !ok || w.Count != v.Count {
            equal = false
            break
        }
    }
}
fmt.Println(equal) // => true

//
// maps.EqualFunc を使う場合
//
isSameCount := func(a, b FruitStock) bool {
    return a.Count == b.Count
}
fmt.Println(maps.EqualFunc(m1, m2, isSameCount)) // => true
fmt.Println(maps.EqualFunc(m1, m3, isSameCount)) // => false

通常の maps.Equal では比較できない構造体や複雑な型の値も、EqualFunc を使えば柔軟に比較できます。

ロジックの分離と再利用性の向上にもつながり、実用性の高い関数です。

Insert: イテレータから map に要素を追加する

maps.Insert は、iter.Seq2[K, V] 型のイテレータから map に複数の要素を一括で追加する関数です。

既存の map にデータを流し込みたいときや、生成したデータをまとめて格納したいときに便利です。

func Insert[Map ~map[K]V, K comparable, V any](m Map, seq iter.Seq2[K, V])

以下は、2 つのスライスから生成したキーと値を map に挿入する例です。

fruits := map[int]string{
    1000: "EXISTING",
}

more := []string{"apple", "banana", "orange"}

//
// maps を使わない場合
//
for i, fruit := range more {
    fruits[i] = fruit
}
fmt.Println(fruits)
// => map[0:apple 1:banana 2:orange 1000:EXISTING]

//
// maps.Insert を使う場合
//
maps.Insert(fruits, slices.All(more))
fmt.Println(fruits)
// => map[0:apple 1:banana 2:orange 1000:EXISTING]

maps.Insert を使えば、手動でループと代入を書く必要がなくなり、処理をよりシンプルで柔軟に記述できます。

Keys: map のすべてのキーを取得する

maps.Keys は、指定した map に含まれるすべてのキーをイテレータとして取得する関数です。

map の内容を処理する際に、キーの一覧だけを取り出したい場合に便利です。

func Keys[Map ~map[K]V, K comparable, V any](m Map) iter.Seq[K]

以下は、map からキーの一覧を取得してスライスに変換する例です。

fruits := map[string]int{
    "apple":  5,
    "banana": 10,
    "orange": 7,
}

//
// maps を使わない場合
//
keys := make([]string, 0, len(fruits))
for k := range fruits {
    keys = append(keys, k)
}
fmt.Println(keys)
// => [apple banana orange]

//
// maps.Keys を使う場合
//
var keysWithMaps []string
for k := range maps.Keys(fruits) {
    keysWithMaps = append(keysWithMaps, k)
}
fmt.Println(keysWithMaps)
// => [apple banana orange]

maps.Keys を使うことで、キーの一覧を簡潔に取得できます。

Go 1.24 では戻り値がイテレータ型(iter.Seq)になっているため、必要に応じてスライスに変換して使うことも可能です。

Values: map のすべての値を取得する

maps.Values は、指定した map に含まれるすべての値をイテレータとして取得する関数です。

map の各値のみを対象にした処理や集計を行いたい場合に便利です。

func Values[Map ~map[K]V, K comparable, V any](m Map) iter.Seq[V]

以下は、map から値の一覧を取得してスライスに変換する例です。

fruits := map[string]int{
    "apple":  5,
    "banana": 10,
    "orange": 7,
}

//
// maps を使わない場合
//
values := make([]int, 0, len(fruits))
for _, v := range fruits {
    values = append(values, v)
}
fmt.Println(values)
// => [5 10 7]

//
// maps.Values を使う場合
//
var valuesWithMaps []int
for v := range maps.Values(fruits) {
    valuesWithMaps = append(valuesWithMaps, v)
}
fmt.Println(valuesWithMaps)
// => [5 10 7]

maps.Values を使えば、値の一覧を簡潔に取り出すことができ、スライスへの変換も容易です。

Keys と同様に、Go 1.24 では戻り値がイテレータ型(iter.Seq)になっているため、柔軟な処理に対応できます。

まとめ

Go の maps パッケージを活用することで、map に対する操作をより簡潔かつ安全に記述できるようになります。

特に、繰り返し記述しがちな for ループや、複雑な比較・変換処理などを関数化して提供してくれるため、コードの可読性や保守性が向上します。

標準ライブラリとして提供されていることから、追加の依存なく利用できる点も大きなメリットです。

ぜひ日々の Go 開発の中で、maps パッケージの関数を取り入れてみてください。

Author

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級