グローバルナビゲーションへ

本文へ

フッターへ

お役立ち情報Blog



Go1.21で追加された「mapsパッケージ」とは?実際にパッケージ内の関数を使うコードを書いてみた。

気づいたらGo1.22がリリースされていましたが、今回は引き続き1.21で追加されたパッケージを調べようと思います。

今回はmapsパッケージについて解説します。

mapsパッケージの概要

Package maps defines various functions useful with maps of any type.
https://pkg.go.dev/golang.org/x/exp/maps

と今回もドキュメントの概要にあるように、mapsパッケージは、あらゆる型のマップに対して役立つ関数を提供しているパッケージのようです。

mapsパッケージの関数

Clone 引数のマップのコピーを返します。
Copy 第二引数のマップのすべてのkey/valueのペアを第一引数にコピーします。
DeleteFunc 引数に渡した関数がtrueを返したkey/valueのペアを削除します。
Equal 二つのマップが同じkey/valueを持っているかを判定します。
EqualFunc 渡した関数によってvalueを比較する方法で、二つのマップが同じkey/valueを持っているかを判定します。

mapsパッケージの関数の使用例

それではこれらの関数を使って実際にコードを書いてみます。

Clone

m1 := map[string]string{
    "a": "a",
}
m2 := maps.Clone(m1)
fmt.Println(m1["a"]) // a
fmt.Println(m2["a"]) // a

m2["a"] = "b"

fmt.Println(m1["a"]) // a
fmt.Println(m2["a"]) // b

このようにマップのコピーが作られます。このコピーは

This is a shallow clone

とあるようにディープコピーではないので、コピー先の変更がコピー元に影響する場合があるようです。

以下のコードが影響が出る場合を調べたものです。

m1 := map[string][]string{
    "a": {"a"},
}

m2 := maps.Clone(m1)

// m1["a"]とm2["a"]の参照先のアドレスが同じ
fmt.Printf("m1[\"a\"] value = %v\n", m1["a"])   // m1["a"] value = [a]
fmt.Printf("m1[\"a\"] pointer = %p\n", m1["a"]) // m1["a"] pointer = 0xc0000a6020
fmt.Printf("m2[\"a\"] value = %v\n", m2["a"])   // m2["a"] value = [a]
fmt.Printf("m2[\"a\"] pointer = %p\n", m2["a"]) // m2["a"] pointer = 0xc0000a6020

// コピー先を変更してもコピー元に影響が出ない場合
m2["a"] = []string{"b"}

// m2["a"]が参照するアドレス自体が変更されるのでコピー元に影響が出ない
fmt.Printf("m1[\"a\"] value = %v\n", m1["a"])   // m1["a"] value = [a]
fmt.Printf("m1[\"a\"] pointer = %p\n", m1["a"]) // m1["a"] pointer = 0xc0000a6020
fmt.Printf("m2[\"a\"] value = %v\n", m2["a"])   // m2["a"] value = [b]
fmt.Printf("m2[\"a\"] pointer = %p\n", m2["a"]) // m2["a"] pointer = 0xc0000a6050

// コピー元に影響が出る場合
m3 := maps.Clone(m1)
m3["a"][0] = "b"

// m1["a"]とm3["a"]が参照するアドレスに保存されている値を変更したので、コピー元にも影響が出る
fmt.Printf("m1[\"a\"] value = %v\n", m1["a"])   // m1["a"] value = [b]
fmt.Printf("m1[\"a\"] pointer = %p\n", m1["a"]) // m1["a"] pointer = 0xc0000a6020
fmt.Printf("m3[\"a\"] value = %v\n", m3["a"])   // m3["a"] value = [b]
fmt.Printf("m3[\"a\"] pointer = %p\n", m3["a"]) // m3["a"] pointer = 0xc0000a6020

Copy

引数にマップを二つ渡し、第二引数のkey/valueペアを第一引数にコピーします。
もしコピー先にコピー元が持っているkeyが存在する場合は上書きされます。

m1 := map[string][]string{
    "a": {"b"},
    "c": {"d"},
}
m2 := map[string][]string{
    "c": {"e", "f", "g"},,
}
// m2["c"]が上書きされる
maps.Copy(m2, m1)
fmt.Println(m1) // map[a:[b] c:[d]]
fmt.Println(m2) // map[a:[b] c:[d]]

// CopyもCloneと同じくshallowなコピーなもよう
m2["a"][0] = "x"
fmt.Println(m1) // map[a:[x] c:[d]]
fmt.Println(m2) // map[a:[x] c:[d]]
m2["a"] = []string{"y"}
fmt.Println(m1) // map[a:[x] c:[d]]
fmt.Println(m2) // map[a:[y] c:[d]]

DeleteFunc

m := map[string]int{
    "alfa":   50,
    "bravo":  51,
    "charie": 99,
    "delta":  12,
    "echo":   3,
}
maps.DeleteFunc(m, func(k string, v int) bool {
    return v > 50
})
fmt.Println(m) // map[alfa:50 delta:12 echo:3]

maps.DeleteFunc(m, func(k string, v int) bool {
    return len(k) > 4
})
fmt.Println(m) // map[alfa:50 echo:3]

削除の条件を判定をするのに、key/valueどちらも利用できます。

Equal

m1 := map[string]string{
    "dog": "Bowwow",
    "cat": "Meow",
}
m2 := map[string]string{
    "dog": "Bowwow",
    "cat": "Meow",
}
m3 := map[string]string{
    "dog": "bowwow",
    "cat": "meow",
}

fmt.Println(maps.Equal(m1, m2)) // true
fmt.Println(maps.Equal(m1, m3)) // false

// comparableを実装していないスライス等には利用できない
//m4 := map[string][]int{
//	"hoge": {1, 2, 3},
//	"fuga": {2, 4, 6},
//}
//m5 := map[string][]int{
//	"hoge": {1, 2, 3},
//	"fuga": {2, 4, 6},
//}
// fmt.Println(maps.Equal(m4, m5))

EqualFunc

m1 := map[string]string{
    "name":       "John Doe",
    "blood type": "A",
}
m2 := map[string]string{
    "name":       "   john doe    ",
    "blood type": "a",
}

var eqFunc = func(v1 string, v2 string) bool {
    return strings.TrimSpace(strings.ToUpper(v1)) == strings.TrimSpace(strings.ToUpper(v2))
}

eq := maps.EqualFunc(m1, m2, eqFunc)
fmt.Println(eq) // true

// keyは==で比較される
m3 := map[string]string{
    "Name":                   "John Doe",
    "       blood type     ": "A",
}
eq = maps.EqualFunc(m1, m3, eqFunc)
fmt.Println(eq) // false

コメントで書いたように、keyに関してはEqualFuncに渡した関数ではなく==で比較されます。

さいごに

mapsパッケージを利用することで今までのようにforループを回してkey/valueを新しいマップにコピーする、といったコードを書かずにスマートにコピーできるようになりました。マップを効率的に操作したい場合は、このパッケージを活用してみてください。

この記事を書いた人

wanderlust
wanderlust事業開発部 web application engineer
これまで農業、士業と経験し、まったく異業種のエンジニアとしてアーティスに入社。
現在は事業開発部でバックエンドエンジニアとして仕事に従事。可読性の高いコードが書けるよう日々勉強中。趣味は一人旅。
この記事のカテゴリ

FOLLOW US

最新の情報をお届けします