Skip to content

Commit 4bb58fd

Browse files
committed
feat: adding RepeatByErr helpers
1 parent 72a33aa commit 4bb58fd

File tree

6 files changed

+186
-0
lines changed

6 files changed

+186
-0
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -980,6 +980,15 @@ slice := lo.RepeatBy(5, func(i int) string {
980980

981981
[[play](https://go.dev/play/p/ozZLCtX_hNU)]
982982

983+
With error handling:
984+
985+
```go
986+
slice, err := lo.RepeatByErr(3, func(i int) (string, error) {
987+
return fmt.Sprintf("item-%d", i), nil
988+
})
989+
// []string{"item-0", "item-1", "item-2"}, <nil>
990+
```
991+
983992
### KeyBy
984993

985994
Transforms a slice or a slice of structs to a map based on a pivot callback.

docs/data/core-repeatby.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ subCategory: slice
77
playUrl: https://go.dev/play/p/ozZLCtX_hNU
88
variantHelpers:
99
- core#slice#repeatby
10+
- core#slice#repeatbyerr
1011
similarHelpers:
1112
- core#slice#times
1213
- core#slice#repeat

docs/data/core-repeatbyerr.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
name: RepeatByErr
3+
slug: repeatbyerr
4+
sourceRef: slice.go#L564
5+
category: core
6+
subCategory: slice
7+
signatures:
8+
- "func RepeatByErr[T any](count int, callback func(index int) (T, error)) ([]T, error)"
9+
variantHelpers:
10+
- core#slice#repeatbyerr
11+
similarHelpers:
12+
- core#slice#repeatby
13+
- core#slice#times
14+
- core#slice#repeat
15+
position: 225
16+
---
17+
18+
Builds a slice by calling the callback N times with the current index. The callback can return an error to stop iteration immediately.
19+
20+
```go
21+
result, err := lo.RepeatByErr(5, func(i int) (int, error) {
22+
return i * i, nil
23+
})
24+
// []int{0, 1, 4, 9, 16}, <nil>
25+
```
26+
27+
Example with error:
28+
29+
```go
30+
result, err := lo.RepeatByErr(5, func(i int) (int, error) {
31+
if i == 3 {
32+
return 0, fmt.Errorf("number 3 is not allowed")
33+
}
34+
return i * i, nil
35+
})
36+
// []int(nil), error("number 3 is not allowed")
37+
```

lo_example_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2781,6 +2781,15 @@ func ExampleRepeatBy() {
27812781
// Output: [0 1 4 9 16]
27822782
}
27832783

2784+
func ExampleRepeatByErr() {
2785+
result, err := RepeatByErr(3, func(i int) (string, error) {
2786+
return fmt.Sprintf("item-%d", i), nil
2787+
})
2788+
2789+
fmt.Printf("%d %v", len(result), err)
2790+
// Output: 3 <nil>
2791+
}
2792+
27842793
func ExampleKeyBy() {
27852794
list := []string{"a", "aa", "aaa"}
27862795

slice.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,22 @@ func RepeatBy[T any](count int, callback func(index int) T) []T {
562562
return result
563563
}
564564

565+
// RepeatByErr builds a slice with values returned by N calls of callback.
566+
// It returns the first error returned by the callback function.
567+
func RepeatByErr[T any](count int, callback func(index int) (T, error)) ([]T, error) {
568+
result := make([]T, 0, count)
569+
570+
for i := 0; i < count; i++ {
571+
r, err := callback(i)
572+
if err != nil {
573+
return nil, err
574+
}
575+
result = append(result, r)
576+
}
577+
578+
return result, nil
579+
}
580+
565581
// KeyBy transforms a slice or a slice of structs to a map based on a pivot callback.
566582
// Play: https://go.dev/play/p/ccUiUL_Lnel
567583
func KeyBy[K comparable, V any](collection []V, iteratee func(item V) K) map[K]V {

slice_test.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1560,6 +1560,120 @@ func TestRepeatBy(t *testing.T) {
15601560
is.Equal([]int{0, 1, 4, 9, 16}, result3)
15611561
}
15621562

1563+
func TestRepeatByErr(t *testing.T) {
1564+
t.Parallel()
1565+
is := assert.New(t)
1566+
1567+
testErr := errors.New("test error")
1568+
1569+
// Table-driven tests
1570+
tests := []struct {
1571+
name string
1572+
count int
1573+
callback func(index int) (int, error)
1574+
wantResult []int
1575+
wantErr bool
1576+
expectedCallbackCount int
1577+
}{
1578+
{
1579+
name: "successful completion",
1580+
count: 5,
1581+
callback: func(i int) (int, error) {
1582+
return i * i, nil
1583+
},
1584+
wantResult: []int{0, 1, 4, 9, 16},
1585+
wantErr: false,
1586+
expectedCallbackCount: 5,
1587+
},
1588+
{
1589+
name: "error at first iteration",
1590+
count: 5,
1591+
callback: func(i int) (int, error) {
1592+
if i == 0 {
1593+
return 0, testErr
1594+
}
1595+
return i * i, nil
1596+
},
1597+
wantResult: nil,
1598+
wantErr: true,
1599+
expectedCallbackCount: 1,
1600+
},
1601+
{
1602+
name: "error at third iteration",
1603+
count: 5,
1604+
callback: func(i int) (int, error) {
1605+
if i == 2 {
1606+
return 0, testErr
1607+
}
1608+
return i * i, nil
1609+
},
1610+
wantResult: nil,
1611+
wantErr: true,
1612+
expectedCallbackCount: 3,
1613+
},
1614+
{
1615+
name: "error at last iteration",
1616+
count: 5,
1617+
callback: func(i int) (int, error) {
1618+
if i == 4 {
1619+
return 0, testErr
1620+
}
1621+
return i * i, nil
1622+
},
1623+
wantResult: nil,
1624+
wantErr: true,
1625+
expectedCallbackCount: 5,
1626+
},
1627+
{
1628+
name: "zero count - empty result",
1629+
count: 0,
1630+
callback: func(i int) (int, error) {
1631+
return i * i, nil
1632+
},
1633+
wantResult: []int{},
1634+
wantErr: false,
1635+
expectedCallbackCount: 0,
1636+
},
1637+
{
1638+
name: "single item success",
1639+
count: 1,
1640+
callback: func(i int) (int, error) {
1641+
return 42, nil
1642+
},
1643+
wantResult: []int{42},
1644+
wantErr: false,
1645+
expectedCallbackCount: 1,
1646+
},
1647+
}
1648+
1649+
for _, tt := range tests {
1650+
tt := tt
1651+
t.Run(tt.name, func(t *testing.T) {
1652+
t.Parallel()
1653+
1654+
// Track callback count to verify early return
1655+
callbackCount := 0
1656+
wrappedCallback := func(i int) (int, error) {
1657+
callbackCount++
1658+
return tt.callback(i)
1659+
}
1660+
1661+
result, err := RepeatByErr(tt.count, wrappedCallback)
1662+
1663+
if tt.wantErr {
1664+
is.ErrorIs(err, testErr)
1665+
is.Nil(result)
1666+
} else {
1667+
is.NoError(err)
1668+
is.Equal(tt.wantResult, result)
1669+
}
1670+
1671+
// Verify callback count matches expected (tests early return)
1672+
is.Equal(tt.expectedCallbackCount, callbackCount, "callback count should match expected")
1673+
})
1674+
}
1675+
}
1676+
15631677
func TestKeyBy(t *testing.T) {
15641678
t.Parallel()
15651679
is := assert.New(t)

0 commit comments

Comments
 (0)