feat: adding CrossJoinByErrX helpers

This commit is contained in:
Samuel Berthe
2026-03-01 19:42:37 +01:00
parent 8295993219
commit 7c55e5b6e3
6 changed files with 655 additions and 1 deletions
+12
View File
@@ -2493,6 +2493,18 @@ result := lo.CrossJoinBy2([]string{"hello", "john", "doe"}, []int{1, 2}, func(a
// "doe - 2"
```
With error handling:
```go
result, err := lo.CrossJoinByErr2([]string{"hello", "john"}, []int{1, 2}, func(a string, b int) (string, error) {
if a == "john" {
return "", fmt.Errorf("john not allowed")
}
return fmt.Sprintf("%s - %d", a, b), nil
})
// []string(nil), error("john not allowed")
```
### Duration
Returns the time taken to execute a function.
+44
View File
@@ -0,0 +1,44 @@
---
name: CrossJoinByErrX
slug: crossjoinbyerrx
sourceRef: tuples.go#L1320
category: core
subCategory: tuple
signatures:
- "func CrossJoinByErr2[A any, B any, Out any](listA []A, listB []B, transform func(a A, b B) (Out, error)) ([]Out, error)"
- "func CrossJoinByErr3[A any, B any, C any, Out any](listA []A, listB []B, listC []C, transform func(a A, b B, c C) (Out, error)) ([]Out, error)"
- "func CrossJoinByErr4[A any, B any, C any, D any, Out any](listA []A, listB []B, listC []C, listD []D, transform func(a A, b B, c C, d D) (Out, error)) ([]Out, error)"
- "func CrossJoinByErr5[A any, B any, C any, D any, E any, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, transform func(a A, b B, c C, d D, e E) (Out, error)) ([]Out, error)"
- "func CrossJoinByErr6[A any, B any, C any, D any, E any, F any, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, transform func(a A, b B, c C, d D, e E, f F) (Out, error)) ([]Out, error)"
- "func CrossJoinByErr7[A any, B any, C any, D any, E any, F any, G any, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, transform func(a A, b B, c C, d D, e E, f F, g G) (Out, error)) ([]Out, error)"
- "func CrossJoinByErr8[A any, B any, C any, D any, E any, F any, G any, H any, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, transform func(a A, b B, c C, d D, e E, f F, g G, h H) (Out, error)) ([]Out, error)"
- "func CrossJoinByErr9[A any, B any, C any, D any, E any, F any, G any, H any, I any, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, listI []I, transform func(a A, b B, c C, d D, e E, f F, g G, h H, i I) (Out, error)) ([]Out, error)"
variantHelpers:
- core#tuple#crossjoinbyerrx
similarHelpers:
- core#tuple#crossjoinbyx
position: 61
---
Computes a cartesian product and projects each combination through a function that can return an error. Stops iteration immediately when an error is encountered and returns the zero value (nil for slices).
Variants: `CrossJoinByErr2..CrossJoinByErr9`
```go
result, err := lo.CrossJoinByErr2([]string{"a", "b"}, []int{1, 2}, func(a string, b int) (string, error) {
if a == "b" {
return "", fmt.Errorf("b not allowed")
}
return fmt.Sprintf("%s-%d", a, b), nil
})
// []string(nil), error("b not allowed")
```
```go
result, err := lo.CrossJoinByErr2([]string{"a", "b"}, []int{1, 2}, func(a string, b int) (string, error) {
return fmt.Sprintf("%s-%d", a, b), nil
})
// []string{"a-1", "a-2", "b-1", "b-2"}, nil
```
Returns an empty list if any input list is empty.
+2 -1
View File
@@ -16,6 +16,7 @@ signatures:
playUrl: https://go.dev/play/p/8Y7btpvuA-C
variantHelpers:
- core#tuple#crossjoinbyx
- core#tuple#crossjoinbyerrx
similarHelpers:
- core#tuple#zipx
- core#tuple#unzipx
@@ -23,7 +24,7 @@ similarHelpers:
- core#tuple#unzipbyx
- core#slice#product
- core#slice#productby
position: 60
position: 62
---
Computes a cartesian product and projects each combination through a function. Variants support 2 up to 9 input slices.
+36
View File
@@ -3828,6 +3828,42 @@ func ExampleCrossJoinBy9() {
// b-4-false-{bar}-4.2-plop-false-42-hello world
}
func ExampleCrossJoinByErr2() {
result, err := CrossJoinByErr2([]string{"a", "b"}, []int{1, 2}, func(a string, b int) (string, error) {
if a == "b" {
return "", fmt.Errorf("b not allowed")
}
return fmt.Sprintf("%v-%v", a, b), nil
})
if err != nil {
fmt.Printf("error: %v\n", err)
return
}
for _, r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// error: b not allowed
}
func ExampleZipByErr2() {
result, err := ZipByErr2([]string{"a", "b", "c"}, []int{1, 2, 3}, func(a string, b int) (string, error) {
if a == "b" {
return "", fmt.Errorf("b is not allowed")
}
return fmt.Sprintf("%v-%v", a, b), nil
})
if err != nil {
fmt.Printf("error: %v\n", err)
return
}
for _, r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// error: b is not allowed
}
func ExampleIntersect() {
result := Intersect([]int{0, 3, 5, 7}, []int{3, 5}, []int{0, 1, 2, 0, 3, 0})
fmt.Printf("%v", result)
+264
View File
@@ -1315,3 +1315,267 @@ func CrossJoinBy9[A, B, C, D, E, F, G, H, I, Out any](listA []A, listB []B, list
return result
}
// CrossJoinByErr2 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// It returns the first error returned by the transform function.
func CrossJoinByErr2[A, B, Out any](listA []A, listB []B, transform func(a A, b B) (Out, error)) ([]Out, error) {
size := len(listA) * len(listB)
if size == 0 {
return []Out{}, nil
}
result := make([]Out, 0, size)
for _, a := range listA {
for _, b := range listB {
r, err := transform(a, b)
if err != nil {
return nil, err
}
result = append(result, r)
}
}
return result, nil
}
// CrossJoinByErr3 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// It returns the first error returned by the transform function.
func CrossJoinByErr3[A, B, C, Out any](listA []A, listB []B, listC []C, transform func(a A, b B, c C) (Out, error)) ([]Out, error) {
size := len(listA) * len(listB) * len(listC)
if size == 0 {
return []Out{}, nil
}
result := make([]Out, 0, size)
for _, a := range listA {
for _, b := range listB {
for _, c := range listC {
r, err := transform(a, b, c)
if err != nil {
return nil, err
}
result = append(result, r)
}
}
}
return result, nil
}
// CrossJoinByErr4 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// It returns the first error returned by the transform function.
func CrossJoinByErr4[A, B, C, D, Out any](listA []A, listB []B, listC []C, listD []D, transform func(a A, b B, c C, d D) (Out, error)) ([]Out, error) {
size := len(listA) * len(listB) * len(listC) * len(listD)
if size == 0 {
return []Out{}, nil
}
result := make([]Out, 0, size)
for _, a := range listA {
for _, b := range listB {
for _, c := range listC {
for _, d := range listD {
r, err := transform(a, b, c, d)
if err != nil {
return nil, err
}
result = append(result, r)
}
}
}
}
return result, nil
}
// CrossJoinByErr5 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// It returns the first error returned by the transform function.
func CrossJoinByErr5[A, B, C, D, E, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, transform func(a A, b B, c C, d D, e E) (Out, error)) ([]Out, error) {
size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE)
if size == 0 {
return []Out{}, nil
}
result := make([]Out, 0, size)
for _, a := range listA {
for _, b := range listB {
for _, c := range listC {
for _, d := range listD {
for _, e := range listE {
r, err := transform(a, b, c, d, e)
if err != nil {
return nil, err
}
result = append(result, r)
}
}
}
}
}
return result, nil
}
// CrossJoinByErr6 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// It returns the first error returned by the transform function.
func CrossJoinByErr6[A, B, C, D, E, F, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, transform func(a A, b B, c C, d D, e E, f F) (Out, error)) ([]Out, error) {
size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF)
if size == 0 {
return []Out{}, nil
}
result := make([]Out, 0, size)
for _, a := range listA {
for _, b := range listB {
for _, c := range listC {
for _, d := range listD {
for _, e := range listE {
for _, f := range listF {
r, err := transform(a, b, c, d, e, f)
if err != nil {
return nil, err
}
result = append(result, r)
}
}
}
}
}
}
return result, nil
}
// CrossJoinByErr7 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// It returns the first error returned by the transform function.
func CrossJoinByErr7[A, B, C, D, E, F, G, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, transform func(a A, b B, c C, d D, e E, f F, g G) (Out, error)) ([]Out, error) {
size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF) * len(listG)
if size == 0 {
return []Out{}, nil
}
result := make([]Out, 0, size)
for _, a := range listA {
for _, b := range listB {
for _, c := range listC {
for _, d := range listD {
for _, e := range listE {
for _, f := range listF {
for _, g := range listG {
r, err := transform(a, b, c, d, e, f, g)
if err != nil {
return nil, err
}
result = append(result, r)
}
}
}
}
}
}
}
return result, nil
}
// CrossJoinByErr8 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// It returns the first error returned by the transform function.
func CrossJoinByErr8[A, B, C, D, E, F, G, H, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, transform func(a A, b B, c C, d D, e E, f F, g G, h H) (Out, error)) ([]Out, error) {
size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF) * len(listG) * len(listH)
if size == 0 {
return []Out{}, nil
}
result := make([]Out, 0, size)
for _, a := range listA {
for _, b := range listB {
for _, c := range listC {
for _, d := range listD {
for _, e := range listE {
for _, f := range listF {
for _, g := range listG {
for _, h := range listH {
r, err := transform(a, b, c, d, e, f, g, h)
if err != nil {
return nil, err
}
result = append(result, r)
}
}
}
}
}
}
}
}
return result, nil
}
// CrossJoinByErr9 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// It returns the first error returned by the transform function.
func CrossJoinByErr9[A, B, C, D, E, F, G, H, I, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, listI []I, transform func(a A, b B, c C, d D, e E, f F, g G, h H, i I) (Out, error)) ([]Out, error) {
size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF) * len(listG) * len(listH) * len(listI)
if size == 0 {
return []Out{}, nil
}
result := make([]Out, 0, size)
for _, a := range listA {
for _, b := range listB {
for _, c := range listC {
for _, d := range listD {
for _, e := range listE {
for _, f := range listF {
for _, g := range listG {
for _, h := range listH {
for _, i := range listI {
r, err := transform(a, b, c, d, e, f, g, h, i)
if err != nil {
return nil, err
}
result = append(result, r)
}
}
}
}
}
}
}
}
}
return result, nil
}
+297
View File
@@ -982,3 +982,300 @@ func TestCrossJoinBy(t *testing.T) {
results7 := CrossJoinBy2(listOne, mixedList, T2[string, any])
is.Equal([]Tuple2[string, any]{T2[string, any]("a", 9.6), T2[string, any]("a", 4), T2[string, any]("a", "foobar"), T2[string, any]("b", 9.6), T2[string, any]("b", 4), T2[string, any]("b", "foobar"), T2[string, any]("c", 9.6), T2[string, any]("c", 4), T2[string, any]("c", "foobar")}, results7)
}
func TestCrossJoinByErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
// Test CrossJoinByErr2
t.Run("CrossJoinByErr2", func(t *testing.T) {
t.Parallel()
tests := []struct {
name string
listA []string
listB []int
transform func(a string, b int) (string, error)
wantResult []string
wantErr bool
errMsg string
expectedCallbackCount int
}{
{
name: "successful transformation",
listA: []string{"a", "b"},
listB: []int{1, 2},
transform: func(a string, b int) (string, error) {
return a + "-" + strconv.Itoa(b), nil
},
wantResult: []string{"a-1", "a-2", "b-1", "b-2"},
wantErr: false,
expectedCallbackCount: 4,
},
{
name: "error stops iteration early",
listA: []string{"a", "b"},
listB: []int{1, 2},
transform: func(a string, b int) (string, error) {
if a == "b" {
return "", errors.New("b not allowed")
}
return a + "-" + strconv.Itoa(b), nil
},
wantResult: nil,
wantErr: true,
errMsg: "b not allowed",
expectedCallbackCount: 3, // a-1, a-2, then b-1 errors
},
{
name: "empty list returns empty result",
listA: []string{},
listB: []int{1, 2},
transform: func(a string, b int) (string, error) { return a + "-" + strconv.Itoa(b), nil },
wantResult: []string{},
wantErr: false,
expectedCallbackCount: 0,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
callbackCount := 0
transform := func(a string, b int) (string, error) {
callbackCount++
return tt.transform(a, b)
}
result, err := CrossJoinByErr2(tt.listA, tt.listB, transform)
is.Equal(tt.wantResult, result)
if tt.wantErr {
is.Error(err)
if tt.errMsg != "" {
is.ErrorContains(err, tt.errMsg)
}
} else {
is.NoError(err)
}
is.Equal(tt.expectedCallbackCount, callbackCount)
})
}
})
// Test CrossJoinByErr3
t.Run("CrossJoinByErr3", func(t *testing.T) {
t.Parallel()
tests := []struct {
name string
listA []string
listB []int
listC []bool
transform func(a string, b int, c bool) (string, error)
wantResult []string
wantErr bool
expectedCallbackCount int
}{
{
name: "successful transformation",
listA: []string{"a", "b"},
listB: []int{1, 2},
listC: []bool{true, false},
transform: func(a string, b int, c bool) (string, error) {
return a + "-" + strconv.Itoa(b), nil
},
wantResult: []string{"a-1", "a-1", "a-2", "a-2", "b-1", "b-1", "b-2", "b-2"},
wantErr: false,
expectedCallbackCount: 8,
},
{
name: "error stops iteration early",
listA: []string{"a", "b"},
listB: []int{1, 2},
listC: []bool{true, false},
transform: func(a string, b int, c bool) (string, error) {
if a == "b" && b == 2 {
return "", errors.New("error")
}
return a + "-" + strconv.Itoa(b), nil
},
wantResult: nil,
wantErr: true,
expectedCallbackCount: 7,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
callbackCount := 0
transform := func(a string, b int, c bool) (string, error) {
callbackCount++
return tt.transform(a, b, c)
}
result, err := CrossJoinByErr3(tt.listA, tt.listB, tt.listC, transform)
is.Equal(tt.wantResult, result)
if tt.wantErr {
is.Error(err)
} else {
is.NoError(err)
}
is.Equal(tt.expectedCallbackCount, callbackCount)
})
}
})
// Test CrossJoinByErr4
t.Run("CrossJoinByErr4", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
callbackCount := 0
result, err := CrossJoinByErr4(
[]string{"a", "b"},
[]int{1, 2},
[]bool{true},
[]float32{1.1},
func(a string, b int, c bool, d float32) (string, error) {
callbackCount++
return a + "-" + strconv.Itoa(b), nil
},
)
is.Equal(4, len(result))
is.NoError(err)
is.Equal(4, callbackCount)
})
// Test CrossJoinByErr5
t.Run("CrossJoinByErr5", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
callbackCount := 0
result, err := CrossJoinByErr5(
[]string{"a", "b"},
[]int{1},
[]bool{true},
[]float32{1.1},
[]float64{2.2},
func(a string, b int, c bool, d float32, e float64) (string, error) {
callbackCount++
return a + "-" + strconv.Itoa(b), nil
},
)
is.Equal(2, len(result))
is.NoError(err)
is.Equal(2, callbackCount)
})
// Test CrossJoinByErr6
t.Run("CrossJoinByErr6", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
callbackCount := 0
result, err := CrossJoinByErr6(
[]string{"a"},
[]int{1},
[]bool{true},
[]float32{1.1},
[]float64{2.2},
[]int8{3},
func(a string, b int, c bool, d float32, e float64, f int8) (string, error) {
callbackCount++
return a + "-" + strconv.Itoa(b), nil
},
)
is.Equal(1, len(result))
is.NoError(err)
is.Equal(1, callbackCount)
})
// Test CrossJoinByErr7
t.Run("CrossJoinByErr7", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
callbackCount := 0
result, err := CrossJoinByErr7(
[]string{"a"},
[]int{1},
[]bool{true},
[]float32{1.1},
[]float64{2.2},
[]int8{3},
[]int16{4},
func(a string, b int, c bool, d float32, e float64, f int8, g int16) (string, error) {
callbackCount++
return a + "-" + strconv.Itoa(b), nil
},
)
is.Equal(1, len(result))
is.NoError(err)
is.Equal(1, callbackCount)
})
// Test CrossJoinByErr8
t.Run("CrossJoinByErr8", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
callbackCount := 0
result, err := CrossJoinByErr8(
[]string{"a"},
[]int{1},
[]bool{true},
[]float32{1.1},
[]float64{2.2},
[]int8{3},
[]int16{4},
[]int32{5},
func(a string, b int, c bool, d float32, e float64, f int8, g int16, h int32) (string, error) {
callbackCount++
return a + "-" + strconv.Itoa(b), nil
},
)
is.Equal(1, len(result))
is.NoError(err)
is.Equal(1, callbackCount)
})
// Test CrossJoinByErr9
t.Run("CrossJoinByErr9", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
callbackCount := 0
result, err := CrossJoinByErr9(
[]string{"a"},
[]int{1},
[]bool{true},
[]float32{1.1},
[]float64{2.2},
[]int8{3},
[]int16{4},
[]int32{5},
[]int64{6},
func(a string, b int, c bool, d float32, e float64, f int8, g int16, h int32, i int64) (string, error) {
callbackCount++
return a + "-" + strconv.Itoa(b), nil
},
)
is.Equal(1, len(result))
is.NoError(err)
is.Equal(1, callbackCount)
})
}