feat: adding lo.PartitionBy + lop.PartitionBy + lo.GroupBy

This commit is contained in:
Samuel Berthe
2022-03-05 18:44:39 +01:00
parent 9524c046c6
commit 0c3545daf3
7 changed files with 220 additions and 16 deletions

View File

@@ -54,6 +54,7 @@ Supported helpers for slices:
- UniqBy
- GroupBy
- Chunk
- PartitionBy
- Flatten
- Shuffle
- Reverse
@@ -191,7 +192,7 @@ uniqValues := lo.Uniq[int]([]int{1, 2, 2, 1})
Returns a duplicate-free version of an array, in which only the first occurrence of each element is kept. The order of result values is determined by the order they occur in the array. It accepts `iteratee` which is invoked for each element in array to generate the criterion by which uniqueness is computed.
```go
uniqValues := lo.UniqBy[int, int]([]int{0, 1, 2, 3, 4, 5}, func (i int) int {
uniqValues := lo.UniqBy[int, int]([]int{0, 1, 2, 3, 4, 5}, func(i int) int {
return i%3
})
// []int{0, 1, 2}
@@ -202,7 +203,18 @@ uniqValues := lo.UniqBy[int, int]([]int{0, 1, 2, 3, 4, 5}, func (i int) int {
Returns an object composed of keys generated from the results of running each element of collection through iteratee.
```go
groups := GroupBy[int, int]([]int{0, 1, 2, 3, 4, 5}, func (i int) int {
groups := GroupBy[int, int]([]int{0, 1, 2, 3, 4, 5}, func(i int) int {
return i%3
})
// map[int][]int{0: []int{0, 3}, 1: []int{1, 4}, 2: []int{2, 5}}
```
Parallel processing: like `lo.GroupBy()`, but callback is called in goroutine.
```go
import lop "github.com/samber/lo/parallel"
lop.GroupBy[int, int]([]int{0, 1, 2, 3, 4, 5}, func(i int) int {
return i%3
})
// map[int][]int{0: []int{0, 3}, 1: []int{1, 4}, 2: []int{2, 5}}
@@ -226,6 +238,38 @@ lo.Chunk[int]([]int{0}, 2)
// [][]int{{0}}
```
### PartitionBy
Returns an array of elements split into groups. The order of grouped values is determined by the order they occur in collection. The grouping is generated from the results of running each element of collection through iteratee.
```go
partitions := lo.PartitionBy[int, string]([]int{-2, -1, 0, 1, 2, 3, 4, 5}, func(x int) string {
if x < 0 {
return "negative"
} else if x%2 == 0 {
return "even"
}
return "odd"
})
// [][]int{{-2, -1}, {0, 2, 4}, {1, 3, 5}}
```
Parallel processing: like `lo.PartitionBy()`, but callback is called in goroutine. Results are returned in the same order.
```go
import lop "github.com/samber/lo/parallel"
partitions := lo.PartitionBy[int, string]([]int{-2, -1, 0, 1, 2, 3, 4, 5}, func(x int) string {
if x < 0 {
return "negative"
} else if x%2 == 0 {
return "even"
}
return "odd"
})
// [][]int{{-2, -1}, {0, 2, 4}, {1, 3, 5}}
```
### Flatten
Returns an array a single level deep.
@@ -275,7 +319,7 @@ initializedSlice := lo.Fill[foo]([]foo{foo{"a"}, foo{"a"}}, foo{"b"})
Transforms a slice or an array of structs to a map based on a pivot callback.
```go
m := lo.ToMap[int, string]([]string{"a", "aa", "aaa"}, func (str string) int {
m := lo.ToMap[int, string]([]string{"a", "aa", "aaa"}, func(str string) int {
return len(str)
})
// map[int]string{1: "a", 2: "aa", 3: "aaa"}
@@ -543,13 +587,13 @@ Using callbacks:
```go
result := lo.Switch[int, string](1).
CaseF(1, func () string {
CaseF(1, func() string {
return "1"
}).
CaseF(2, func () string {
CaseF(2, func() string {
return "2"
}).
DefaultF(func () string {
DefaultF(func() string {
return "3"
})
// "1"

15
go.mod
View File

@@ -2,12 +2,17 @@ module github.com/samber/lo
go 1.18
require github.com/stretchr/testify v1.7.0
require (
github.com/stretchr/testify v1.7.0
github.com/thoas/go-funk v0.9.1
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17
)
require (
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/thoas/go-funk v0.9.1 // indirect
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)

16
go.sum
View File

@@ -1,5 +1,13 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -10,8 +18,10 @@ github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M=
github.com/thoas/go-funk v0.9.1/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q=
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM=
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -43,3 +43,70 @@ func ForEach[T any](collection []T, iteratee func(T, int)) {
wg.Wait()
}
// GroupBy returns an object composed of keys generated from the results of running each element of collection through iteratee.
// `iteratee` is call in parallel.
func GroupBy[T any, U comparable](collection []T, iteratee func(T) U) map[U][]T {
result := map[U][]T{}
var mu sync.Mutex
var wg sync.WaitGroup
wg.Add(len(collection))
for _, item := range collection {
go func (_item T) {
key := iteratee(_item)
mu.Lock()
if _, ok := result[key]; !ok {
result[key] = []T{}
}
result[key] = append(result[key], _item)
mu.Unlock()
wg.Done()
}(item)
}
wg.Wait()
return result
}
// PartitionBy returns an array of elements split into groups. The order of grouped values is
// determined by the order they occur in collection. The grouping is generated from the results
// of running each element of collection through iteratee.
// `iteratee` is call in parallel.
func PartitionBy[T any, K comparable](collection []T, iteratee func (x T) K) [][]T {
result := [][]T{}
seen := map[K]int{}
var mu sync.Mutex
var wg sync.WaitGroup
wg.Add(len(collection))
for _, item := range collection {
go func(_item T) {
key := iteratee(_item)
mu.Lock()
resultIndex, ok := seen[key]
if !ok {
resultIndex = len(result)
seen[key] = resultIndex
result = append(result, []T{})
}
result[resultIndex] = append(result[resultIndex], _item)
mu.Unlock()
}(item)
}
wg.Wait()
return result
}

View File

@@ -22,3 +22,37 @@ func TestMap(t *testing.T) {
is.Equal(result1, []string{"Hello", "Hello", "Hello", "Hello"})
is.Equal(result2, []string{"1", "2", "3", "4"})
}
func TestGroupBy(t *testing.T) {
is := assert.New(t)
result1 := GroupBy[int, int]([]int{0, 1, 2, 3, 4, 5}, func(i int) int {
return i % 3
})
is.Equal(len(result1), 3)
is.Equal(result1, map[int][]int{
0: []int{0, 3},
1: []int{1, 4},
2: []int{2, 5},
})
}
func TestPartitionBy(t *testing.T) {
is := assert.New(t)
oddEven := func (x int) string {
if x < 0 {
return "negative"
} else if x%2 == 0 {
return "even"
}
return "odd"
}
result1 := PartitionBy[int, string]([]int{-2, -1, 0, 1, 2, 3, 4, 5}, oddEven)
result2 := PartitionBy[int, string]([]int{}, oddEven)
is.Equal(result1, [][]int{{-2, -1}, {0, 2, 4}, {1, 3, 5}})
is.Equal(result2, [][]int{})
}

View File

@@ -107,8 +107,6 @@ func Chunk[T any](collection []T, size int) [][]T {
}
result := make([][]T, 0, len(collection)/2+1)
// result := [][]T{}
length := len(collection)
for i := 0; i < length; i++ {
@@ -124,6 +122,33 @@ func Chunk[T any](collection []T, size int) [][]T {
return result
}
// PartitionBy returns an array of elements split into groups. The order of grouped values is
// determined by the order they occur in collection. The grouping is generated from the results
// of running each element of collection through iteratee.
func PartitionBy[T any, K comparable](collection []T, iteratee func(x T) K) [][]T {
result := [][]T{}
seen := map[K]int{}
for _, item := range collection {
key := iteratee(item)
resultIndex, ok := seen[key]
if !ok {
resultIndex = len(result)
seen[key] = resultIndex
result = append(result, []T{})
}
result[resultIndex] = append(result[resultIndex], item)
}
return result
// unordered:
// groups := GroupBy[T, K](collection, iteratee)
// return Values[K, []T](groups)
}
// Flattens returns an array a single level deep.
func Flatten[T any](collection [][]T) []T {
result := []T{}

View File

@@ -102,6 +102,25 @@ func TestChunk(t *testing.T) {
is.Equal(result4, [][]int{{0}})
}
func TestPartitionBy(t *testing.T) {
is := assert.New(t)
oddEven := func (x int) string {
if x < 0 {
return "negative"
} else if x%2 == 0 {
return "even"
}
return "odd"
}
result1 := PartitionBy[int, string]([]int{-2, -1, 0, 1, 2, 3, 4, 5}, oddEven)
result2 := PartitionBy[int, string]([]int{}, oddEven)
is.Equal(result1, [][]int{{-2, -1}, {0, 2, 4}, {1, 3, 5}})
is.Equal(result2, [][]int{})
}
func TestFlatten(t *testing.T) {
is := assert.New(t)