mirror of
https://github.com/samber/lo.git
synced 2025-10-05 07:56:51 +08:00
feat: adding attempt + repeat + times
This commit is contained in:
86
README.md
86
README.md
@@ -50,6 +50,7 @@ Supported helpers for slices:
|
||||
- Map
|
||||
- Reduce
|
||||
- ForEach
|
||||
- Times
|
||||
- Uniq
|
||||
- UniqBy
|
||||
- GroupBy
|
||||
@@ -59,6 +60,7 @@ Supported helpers for slices:
|
||||
- Shuffle
|
||||
- Reverse
|
||||
- Fill
|
||||
- Repeat
|
||||
- ToMap
|
||||
|
||||
Supported helpers for maps:
|
||||
@@ -99,6 +101,7 @@ Other functional programming helpers:
|
||||
- Switch / Case / Default
|
||||
- ToPtr
|
||||
- ToSlicePtr
|
||||
- Attempt
|
||||
|
||||
Constraints:
|
||||
|
||||
@@ -183,6 +186,30 @@ lop.ForEach[string]([]string{"hello", "world"}, func(x string, _ int) {
|
||||
// prints "hello\nworld\n" or "world\nhello\n"
|
||||
```
|
||||
|
||||
### Times
|
||||
|
||||
Times invokes the iteratee n times, returning an array of the results of each invocation. The iteratee is invoked with index as argument.
|
||||
|
||||
```go
|
||||
import "github.com/samber/lo"
|
||||
|
||||
lo.Times[string](3, func(i int) string {
|
||||
return strconv.FormatInt(int64(i), 10)
|
||||
})
|
||||
// []string{"0", "1", "2"}
|
||||
```
|
||||
|
||||
Parallel processing: like `lo.Times()`, but callback is called in goroutine.
|
||||
|
||||
```go
|
||||
import lop "github.com/samber/lo/parallel"
|
||||
|
||||
lop.Times[string](3, func(i int) string {
|
||||
return strconv.FormatInt(int64(i), 10)
|
||||
})
|
||||
// []string{"0", "1", "2"}
|
||||
```
|
||||
|
||||
### Uniq
|
||||
|
||||
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.
|
||||
@@ -208,7 +235,9 @@ 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 {
|
||||
import lo "github.com/samber/lo"
|
||||
|
||||
groups := lo.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}}
|
||||
@@ -248,6 +277,8 @@ lo.Chunk[int]([]int{0}, 2)
|
||||
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
|
||||
import lo "github.com/samber/lo"
|
||||
|
||||
partitions := lo.PartitionBy[int, string]([]int{-2, -1, 0, 1, 2, 3, 4, 5}, func(x int) string {
|
||||
if x < 0 {
|
||||
return "negative"
|
||||
@@ -319,6 +350,23 @@ initializedSlice := lo.Fill[foo]([]foo{foo{"a"}, foo{"a"}}, foo{"b"})
|
||||
// []foo{foo{"b"}, foo{"b"}}
|
||||
```
|
||||
|
||||
### Repeat
|
||||
|
||||
Builds a slice with N copies of initial value.
|
||||
|
||||
```go
|
||||
type foo struct {
|
||||
bar string
|
||||
}
|
||||
|
||||
func (f foo) Clone() foo {
|
||||
return foo{f.bar}
|
||||
}
|
||||
|
||||
initializedSlice := lo.Repeat[foo](2, foo{"a"})
|
||||
// []foo{foo{"a"}, foo{"a"}}
|
||||
```
|
||||
|
||||
### ToMap
|
||||
|
||||
Transforms a slice or an array of structs to a map based on a pivot callback.
|
||||
@@ -643,6 +691,42 @@ ptr := lo.ToSlicePtr[string]([]string{"hello", "world"})
|
||||
// []*string{"hello", "world"}
|
||||
```
|
||||
|
||||
### Attempt
|
||||
|
||||
Invokes a function N times until it returns valid output. Returning either the caught error or nil. When first argument is less than `1`, the function runs until a sucessfull response is returned.
|
||||
|
||||
```go
|
||||
iter, err := lo.Attempt(42, func(i int) error {
|
||||
if i == 5 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("failed")
|
||||
})
|
||||
// 6
|
||||
// nil
|
||||
|
||||
iter, err := lo.Attempt(2, func(i int) error {
|
||||
if i == 5 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("failed")
|
||||
})
|
||||
// 2
|
||||
// error "failed"
|
||||
|
||||
iter, err := lo.Attempt(0, func(i int) error {
|
||||
if i < 42 {
|
||||
return fmt.Errorf("failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
// 43
|
||||
// nil
|
||||
```
|
||||
|
||||
## 🛩 Benchmark
|
||||
|
||||
We executed a simple benchmark with the a dead-simple `lo.Map` loop:
|
||||
|
@@ -44,6 +44,33 @@ func ForEach[T any](collection []T, iteratee func(T, int)) {
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// Times invokes the iteratee n times, returning an array of the results of each invocation.
|
||||
// The iteratee is invoked with index as argument.
|
||||
// `iteratee` is call in parallel.
|
||||
func Times[T any](count int, iteratee func(int) T) []T {
|
||||
result := make([]T, count)
|
||||
|
||||
var mu sync.Mutex
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(count)
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
go func(_i int) {
|
||||
item := iteratee(_i)
|
||||
|
||||
mu.Lock()
|
||||
result[_i] = item
|
||||
mu.Unlock()
|
||||
|
||||
wg.Done()
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
@@ -23,6 +23,17 @@ func TestMap(t *testing.T) {
|
||||
is.Equal(result2, []string{"1", "2", "3", "4"})
|
||||
}
|
||||
|
||||
func TestTimes(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
result1 := Times[string](3, func(i int) string {
|
||||
return strconv.FormatInt(int64(i), 10)
|
||||
})
|
||||
|
||||
is.Equal(len(result1), 3)
|
||||
is.Equal(result1, []string{"0", "1", "2"})
|
||||
}
|
||||
|
||||
func TestGroupBy(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
@@ -30,8 +41,8 @@ func TestGroupBy(t *testing.T) {
|
||||
return i % 3
|
||||
})
|
||||
|
||||
is.Equal(len(result1), 3)
|
||||
is.Equal(result1, map[int][]int{
|
||||
is.EqualValues(len(result1), 3)
|
||||
is.EqualValues(result1, map[int][]int{
|
||||
0: []int{0, 3},
|
||||
1: []int{1, 4},
|
||||
2: []int{2, 5},
|
||||
|
16
retry.go
Normal file
16
retry.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package lo
|
||||
|
||||
// Attempt invokes a function N times until it returns valid output. Returning either the caught error or nil. When first argument is less than `1`, the function runs until a sucessfull response is returned.
|
||||
func Attempt(maxIteration int, f func(int) error) (int, error) {
|
||||
var err error
|
||||
|
||||
for i := 0; maxIteration <= 0 || i < maxIteration; i++ {
|
||||
// for retries >= 0 {
|
||||
err = f(i)
|
||||
if err == nil {
|
||||
return i + 1, nil
|
||||
}
|
||||
}
|
||||
|
||||
return maxIteration, err
|
||||
}
|
48
retry_test.go
Normal file
48
retry_test.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package lo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAttempt(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
err := fmt.Errorf("failed")
|
||||
|
||||
iter1, err1 := Attempt(42, func(i int) error {
|
||||
return nil
|
||||
})
|
||||
iter2, err2 := Attempt(42, func(i int) error {
|
||||
if i == 5 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
iter3, err3 := Attempt(2, func(i int) error {
|
||||
if i == 5 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
iter4, err4 := Attempt(0, func(i int) error {
|
||||
if i < 42 {
|
||||
return fmt.Errorf("failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
is.Equal(iter1, 1)
|
||||
is.Equal(err1, nil)
|
||||
is.Equal(iter2, 6)
|
||||
is.Equal(err2, nil)
|
||||
is.Equal(iter3, 2)
|
||||
is.Equal(err3, err)
|
||||
is.Equal(iter4, 43)
|
||||
is.Equal(err4, nil)
|
||||
}
|
23
slice.go
23
slice.go
@@ -43,6 +43,18 @@ func ForEach[T any](collection []T, iteratee func(T, int)) {
|
||||
}
|
||||
}
|
||||
|
||||
// Times invokes the iteratee n times, returning an array of the results of each invocation.
|
||||
// The iteratee is invoked with index as argument.
|
||||
func Times[T any](count int, iteratee func(int) T) []T {
|
||||
result := make([]T, count)
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
result[i] = iteratee(i)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Uniq 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.
|
||||
func Uniq[T comparable](collection []T) []T {
|
||||
@@ -193,6 +205,17 @@ func Fill[T Clonable[T]](collection []T, initial T) []T {
|
||||
return result
|
||||
}
|
||||
|
||||
// Repeat builds a slice with N copies of initial value.
|
||||
func Repeat[T Clonable[T]](count int, initial T) []T {
|
||||
result := make([]T, 0, count)
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
result = append(result, initial.Clone())
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ToMap transforms a slice or an array of structs to a map based on a pivot callback.
|
||||
func ToMap[K comparable, V any](collection []V, iteratee func(V) K) map[K]V {
|
||||
result := make(map[K]V, len(collection))
|
||||
|
@@ -7,6 +7,14 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type foo struct {
|
||||
bar string
|
||||
}
|
||||
|
||||
func (f foo) Clone() foo {
|
||||
return foo{f.bar}
|
||||
}
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
@@ -39,6 +47,17 @@ func TestMap(t *testing.T) {
|
||||
is.Equal(result2, []string{"1", "2", "3", "4"})
|
||||
}
|
||||
|
||||
func TestTimes(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
result1 := Times[string](3, func(i int) string {
|
||||
return strconv.FormatInt(int64(i), 10)
|
||||
})
|
||||
|
||||
is.Equal(len(result1), 3)
|
||||
is.Equal(result1, []string{"0", "1", "2"})
|
||||
}
|
||||
|
||||
func TestReduce(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
@@ -151,14 +170,6 @@ func TestReverse(t *testing.T) {
|
||||
is.Equal(result3, []int{})
|
||||
}
|
||||
|
||||
type foo struct {
|
||||
bar string
|
||||
}
|
||||
|
||||
func (f foo) Clone() foo {
|
||||
return foo{f.bar}
|
||||
}
|
||||
|
||||
func TestFill(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
@@ -169,6 +180,16 @@ func TestFill(t *testing.T) {
|
||||
is.Equal(result2, []foo{})
|
||||
}
|
||||
|
||||
func TestRepeat(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
result1 := Repeat[foo](2, foo{"a"})
|
||||
result2 := Repeat[foo](0, foo{"a"})
|
||||
|
||||
is.Equal(result1, []foo{foo{"a"}, foo{"a"}})
|
||||
is.Equal(result2, []foo{})
|
||||
}
|
||||
|
||||
func TestToMap(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
|
Reference in New Issue
Block a user