go言語のライブラリ、samber/loのGroupByが便利!

はじめに

go言語で実装をしていると、特定の条件に従ってスライスを複数のスライスに分割したい、というケースに遭遇します。

イメージとしてはこんな感じです。

sliceA, sliceB := SplitSlice(slice)

素直に実装するならば、関数内部で条件分岐して対応しているケースが多いのではないでしょうか?

func SplitSlice(s []int) ([]int, []int) {
    sliceA := make([]int,0)
    sliceB := make([]int,0)

    for _, x := range slice {
        if condA {
            sliceA = append(sliceA, x)
        }
        if condB {
            sliceB = append(sliceB, x)
        }
    }
    return sliceA, sliceB
}

samber/lo

こういったケースは samber/lo のGroupByを使用すれば、簡単に実装できます。

以下はREADMEに書いているサンプルです。

func main() {
    list := []int{0, 1, 2, 3, 4, 5}

    result := lo.GroupBy(list, func(i int) int {
        return i % 3
    })

    for _, item := range result {
        fmt.Printf("%v\n", item)
        // [0 3]
        // [1 4]
        // [2 5]
    }
}

https://github.com/samber/lo#groupby

正直これだけだと、すぐに理解するのは難しいと思います。もう少し実際にありそうなサンプルコードを作ってみました。

userにはそれぞれロールがあり、データはどこかのサーバからhttpを通して取得することを想定しています。

今回はそのロールごとにスライスを分割する例を表しています。

type Role string

const (
    Admin     = Role("admin")
    Operator  = Role("operator")
    Developer = Role("developer")
)

type user struct {
    id   string
    role Role
}

func GetUsersFromHogeAPI() []user {
    return []user{
        {
            id:   "1",
            role: Admin,
        },
        {
            id:   "2",
            role: Operator,
        },
        {
            id:   "3",
            role: Developer,
        },
        {
            id:   "4",
            role: Developer,
        },
    }
}

func main() {
    users := GetUsersFromHogeAPI()

    // resultの型はmap[Role][]user
    result := lo.GroupBy(users, func(u user) Role {
        // mapのkeyは以下のreturnで決まる!!
        return u.role
    })

    fmt.Printf("%+v\n", result[Admin])  // [{id:1 role:admin}]
    fmt.Printf("%+v\n", result[Operator]) // [{id:2 role:operator}]
    fmt.Printf("%+v\n", result[Developer]) // [{id:3 role:developer} {id:4 role:developer}]
}

https://go.dev/play/p/sduv6XcX-8U

result変数は map[Role][]user 型となっており、keyにRoleを渡してあげるとuserのスライスが取得できます。

そのほかもこのライブラリにはたくさんの関数が用意されているので、mapやsliceをforで操作するときは使えそうな関数がないか探してみると実装が楽になるかもしれません。

最後に

このライブラリに対する感想は、実装で積極的に使ってもいいと思っています。

理由としては、

  • Filter, Reduce, Mapと見るだけで、データに対してどんな操作をしているのかすぐにわかる
  • 一つ一つの実装は数行なので、たとえメンテナンスされなくなったとしても自前で実装できる

です

実際 GruopByのソースコードを見てみると10行ぐらいであることがわかると思います。

func GroupBy[T any, U comparable](collection []T, iteratee func(item T) U) map[U][]T {
    result := map[U][]T{}

    for _, item := range collection {
        key := iteratee(item)

        result[key] = append(result[key], item)
    }

    return result
}

個人的におすすめなので、実際に使ってみてはどうでしょうか?