feat: adding attempt + repeat + times

This commit is contained in:
Samuel Berthe
2022-03-05 21:24:37 +01:00
parent d90b8528ad
commit 2d3ea84ba6
7 changed files with 241 additions and 11 deletions

View File

@@ -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:

View File

@@ -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 {

View File

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

View File

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

View File

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