17
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Fringe81Advent Calendar 2020

Day 8

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

Last updated at Posted at 2020-12-08

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)
}

参考

17
4
0

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
  3. You can use dark theme
What you can do with signing up
17
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?