LoginSignup
3

More than 1 year has passed since last update.

posted at

updated at

Organization

Golangでコレクションに対して手軽にMapやFindがしたい

golangでは、コレクションを扱うときには主にfor文を使うと思います。
これは「goらしい」書き方だとは思うのですが、純粋な気持ちで書いているとfor文まみれになってしまって、コードが読みづらくなってしまうことも。

golangでも、他言語によくあるMapFindFillterなどの関数があったらいいのに…。

thoas/go-funkはそんな願いを叶えてくれるライブラリです。
MapFindなどの便利なヘルパ関数を提供していて、コレクションに対して手軽これらの操作を行うことができます。
他にもいろいろな便利な関数が提供されているので、今回は特に業務でよく使うものをいくつか紹介していきます。

※ この記事は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)
}

参考
- funk - GoDoc
- thoas/go-funk

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
3