golangでは、コレクションを扱うときには主にfor文を使うと思います。
これは「goらしい」書き方だとは思うのですが、純粋な気持ちで書いているとfor文まみれになってしまって、コードが読みづらくなってしまうことも。
golangでも、他言語によくあるMap
やFind
、Fillter
などの関数があったらいいのに…。
thoas/go-funkはそんな願いを叶えてくれるライブラリです。
Map
やFind
などの便利なヘルパ関数を提供していて、コレクションに対して手軽これらの操作を行うことができます。
他にもいろいろな便利な関数が提供されているので、今回は特に業務でよく使うものをいくつか紹介していきます。
※ この記事はFringe81 Advent Calendar 8日目の記事です。
注意点
最初に注意点を述べますが、go-funk
で提供てされている多くの関数はreflectを使って実装されているため、それらの関数は返り値がinterface{}
型になることに注意が必要です。
typesafeな関数が型ごとに別で提供されている場合が多いので、できるだけそちらを利用するようにしましょう。
加えて、reflectを使って実装されている関数は、typesafeな実装に比べてパフォーマンスで劣るため、繰り返しになりますが使いたい型のtypesafeな関数が提供されている場合はそちらを使用するべきです。
使う
go get github.com/thoas/go-funk
import "github.com/thoas/go-funk"
メソッドの紹介
以下、サンプルコードは公式ドキュメントより転載しています。
サンプルで用いるデータモデル
type Foo struct {
ID int
FirstName string `tag_name:"tag 1"`
LastName string `tag_name:"tag 2"`
Age int `tag_name:"tag 3"`
}
func (f Foo) TableName() string {
return "foo"
}
f := &Foo{
ID: 1,
FirstName: "Foo",
LastName: "Bar",
Age: 30,
}
funk.Contains
iterateeの中に指定された要素があればtrueを返す。
// slice of string
funk.ContainsString([]string{"foo", "bar"}, "bar") // true
// slice of Foo ptr
funk.Contains([]*Foo{f}, f) // true
funk.Contains([]*Foo{f}, nil) // false
b := &Foo{
ID: 2,
FirstName: "Florent",
LastName: "Messa",
Age: 28,
}
funk.Contains([]*Foo{f}, b) // false
// string
funk.Contains("florent", "rent") // true
funk.Contains("florent", "foo") // false
// even map
funk.Contains(map[int]string{1: "Florent"}, 1) // true
typesafeな実装は以下。
- ContainsFloat32
- ContainsFloat64
- ContainsInt
- ContainsInt32
- ContainsInt64
- ContainsString
- ContainsUint
- ContainsUint32
- ContainsUint64
funk.Difference
2つのコレクションの差を返す。
funk.Difference([]int{1, 2, 3, 4}, []int{2, 4, 6}) // []int{1, 3}, []int{6}
funk.Difference([]string{"foo", "bar", "hello", "bar"}, []string{"foo", "bar"}) // []string{"hello"}, []string{}
typesana実装は以下。
- Difference
- DifferenceInt
- DifferenceInt32
- DifferenceInt64
- DifferenceString
- DifferenceUInt
- DifferenceUInt32
- DifferenceUInt64
funk.Filter
スライス内の、特定の条件を満たす要素をフィルタする。
条件は関数として渡す。
r := funk.Filter([]int{1, 2, 3, 4}, func(x int) bool {
return x%2 == 0
}) // []int{2, 4}
typesafeな実装は以下。
- FilterInt
- FilterInt32
- FilterInt64
- FilterFloat32
- FilterFloat64
- FilterString
- FilterUInt
- FilterUInt32
- FilterUInt64
funk.Find
スライス内に、特定の条件を満たす要素があるかどうかを返す。
条件は関数として渡す。
r := funk.Find([]int{1, 2, 3, 4}, func(x int) bool {
return x%2 == 0
}) // 2
typesafeな実装は以下。
- FindInt
- FindInt64
- FindFloat32
- FindFloat64
- FindString
funk.Map
map/sliceに対して特定の操作を行う。
他の型に変換することもできる(map←→slice)
r := funk.Map([]int{1, 2, 3, 4}, func(x int) int {
return x * 2
}) // []int{2, 4, 6, 8}
r := funk.Map([]int{1, 2, 3, 4}, func(x int) string {
return "Hello"
}) // []string{"Hello", "Hello", "Hello", "Hello"}
r = funk.Map([]int{1, 2, 3, 4}, func(x int) (int, int) {
return x, x
}) // map[int]int{1: 1, 2: 2, 3: 3, 4: 4}
mapping := map[int]string{
1: "Florent",
2: "Gilles",
}
r = funk.Map(mapping, func(k int, v string) int {
return k
}) // []int{1, 2}
r = funk.Map(mapping, func(k int, v string) (string, string) {
return fmt.Sprintf("%d", k), v
}) // map[string]string{"1": "Florent", "2": "Gilles"}
返り値がinterface{}
になることに注意。
型変換しないと以降の処理で利用できない。
funk.Keys
Map型のキーをsliceで返す。
funk.Keys(map[string]int{"one": 1, "two": 2}) // []string{"one", "two"} (iteration order is not guaranteed)
typesafeなメソッドはないので、型変換する必要がある。
funk.Uniq
sliceの値を一意にする。
funk.Uniq([]int{0, 1, 1, 2, 3, 0, 0, 12}) // []int{0, 1, 2, 3, 12}
typesafeな実装は以下
- UniqInt
- UniqInt32
- UniqInt64
- UniqFloat32
- UniqFloat64
- UniqString
- UniqUInt
- UniqUInt32
- UniqUInt
funk.Subtract
2つのコレクション間の減算結果を返す。順序を保持する。
funk.Subtract([]int{0, 1, 2, 3, 4}, []int{0, 4}) // []int{1, 2, 3}
funk.Subtract([]int{0, 3, 2, 3, 4}, []int{0, 4}) // []int{3, 2, 3}
stringのみtypesafeな実装が提供されている。 → SubtractString
おまけ interface{}
からの型変換
goでは、実体の型が何であるかを動的にチェックする仕組みとして型アサーションが提供されています。
↓のように書くことができます。
Tは変換したい型で、第1返り値は型アサーションが成功したときの実際の値が、第2返り値は型アサーションが成功したかどうかがboolで返ります。
v, ok := x.(T)
もしくは、変換候補が複数ある場合は、型switch文を使って以下のようにも書けます。
switch x.(type) {
case string:
s := x.(string)
case int:
i := x.(int)
}
参考