mirror of
https://github.com/samber/lo.git
synced 2025-09-26 20:11:13 +08:00
feat: adding lo.PartitionBy + lop.PartitionBy + lo.GroupBy
This commit is contained in:
56
README.md
56
README.md
@@ -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
15
go.mod
@@ -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
16
go.sum
@@ -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=
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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{})
|
||||
}
|
||||
|
29
slice.go
29
slice.go
@@ -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{}
|
||||
|
@@ -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)
|
||||
|
||||
|
Reference in New Issue
Block a user