Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,8 @@ Supported intersection helpers:
- [IntersectBy](#intersectby)
- [Difference](#difference)
- [Union](#union)
- [UnionBy](#unionby)
- [UnionByErr](#unionbyerr)
- [Without](#without)
- [WithoutBy](#withoutby)
- [WithoutEmpty](#withoutempty)
Expand Down Expand Up @@ -3066,6 +3068,34 @@ union := lo.Union([]int{0, 1, 2, 3, 4, 5}, []int{0, 2}, []int{0, 10})
// []int{0, 1, 2, 3, 4, 5, 10}
```

### UnionBy

Returns all distinct elements from predicate returns. Result will not change the order of elements relatively.

```go
predicate := func(i int) int {
return i / 2
}
union := lo.UnionBy(predicate, []int{0, 1, 2, 3, 4, 5}, []int{0, 2, 10}, []int{0, 1, 11})
// []int{0, 2, 4, 10}
```

### UnionByErr

Returns all distinct elements from predicate returns. Result will not change the order of elements relatively. It returns the first error returned by the iteratee.

```go
predicate := func(i int) (int, error) {
if i == 42 {
return 0, errors.New("invalid value")
}
return i / 2, nil
}
union, err := lo.UnionByErr(predicate, []int{0, 1, 2, 3, 4, 5}, []int{0, 2, 10}, []int{0, 1, 11})
// union: []int{0, 2, 4, 10}
// err: nil
```

### Without

Returns a slice excluding all given values.
Expand Down
31 changes: 31 additions & 0 deletions docs/data/core-unionby.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
name: UnionBy
slug: unionby
sourceRef: intersect.go#L260
category: core
subCategory: intersect
playUrl:
variantHelpers:
- core#intersect#unionby
similarHelpers:
- core#intersect#union
- core#intersect#intersect
- core#intersect#intersectby
- core#slice#uniq
- core#slice#uniqby
position: 110
signatures:
- "func UnionBy[T any, V comparable, Slice ~[]T](iteratee func(item T) V, lists ...Slice) Slice"
---

Returns all distinct elements from multiple collections based on a key function. The result maintains the relative order of first occurrences.

```go
lo.UnionBy(func(i int) int { return i / 2 }, []int{0, 1, 2, 3, 4, 5}, []int{0, 2, 10})
// []int{0, 2, 4, 10}
```

```go
lo.UnionBy(func(s string) string { return s[:1] }, []string{"foo", "bar"}, []string{"baz"})
// []string{"foo", "baz"}
```
43 changes: 43 additions & 0 deletions docs/data/core-unionbyerr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
name: UnionByErr
slug: unionbyerr
sourceRef: intersect.go#L285
category: core
subCategory: intersect
playUrl:
variantHelpers:
- core#intersect#unionby
- core#intersect#unionbyerr
similarHelpers:
- core#intersect#unionby
- core#intersect#union
- core#intersect#intersect
- core#intersect#intersectby
- core#slice#uniq
- core#slice#uniqby
position: 111
signatures:
- "func UnionByErr[T any, V comparable, Slice ~[]T](iteratee func(item T) (V, error), lists ...Slice) (Slice, error)"
---

Returns all distinct elements from multiple collections based on a key function that can return an error. The result maintains the relative order of first occurrences. Returns the first error encountered from the iteratee.

```go
lo.UnionByErr(func(i int) (int, error) { return i / 2, nil }, []int{0, 1, 2, 3, 4, 5}, []int{0, 2, 10})
// []int{0, 2, 4, 10}, nil
```

```go
lo.UnionByErr(func(s string) (string, error) { return s[:1], nil }, []string{"foo", "bar"}, []string{"baz"})
// []string{"foo", "baz"}, nil
```

```go
lo.UnionByErr(func(i int) (int, error) {
if i == 42 {
return 0, errors.New("invalid value")
}
return i / 2, nil
}, []int{0, 1, 2}, []int{42})
// []int{0, 1}, error("invalid value")
```
56 changes: 56 additions & 0 deletions intersect.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,62 @@ func Union[T comparable, Slice ~[]T](lists ...Slice) Slice {
return result
}

// UnionBy is like Union except that it accepts iteratee which is invoked for each element of each collections
// to generate the criterion by which uniqueness is computed.
// Result values are chosen from the first collections in which the value occurs.
// Play: TODO.
func UnionBy[T any, V comparable, Slice ~[]T](iteratee func(item T) V, lists ...Slice) Slice {
var capLen int

for _, list := range lists {
capLen += len(list)
}

result := make(Slice, 0, capLen)
seen := make(map[V]struct{}, capLen)

for i := range lists {
for j := range lists[i] {
value := iteratee(lists[i][j])
if _, ok := seen[value]; !ok {
seen[value] = struct{}{}
result = append(result, lists[i][j])
}
}
}

return result
}

// UnionByErr is like UnionBy except that it accepts iteratee which can return an error.
// It returns the first error returned by the iteratee.
// Play: TODO.
func UnionByErr[T any, V comparable, Slice ~[]T](iteratee func(item T) (V, error), lists ...Slice) (Slice, error) {
var capLen int

for _, list := range lists {
capLen += len(list)
}

result := make(Slice, 0, capLen)
seen := make(map[V]struct{}, capLen)

for i := range lists {
for j := range lists[i] {
value, err := iteratee(lists[i][j])
if err != nil {
return nil, err
}
if _, ok := seen[value]; !ok {
seen[value] = struct{}{}
result = append(result, lists[i][j])
}
}
}

return result, nil
}

// Without returns a slice excluding all given values.
// Play: https://go.dev/play/p/PcAVtYJsEsS
func Without[T comparable, Slice ~[]T](collection Slice, exclude ...T) Slice {
Expand Down
86 changes: 86 additions & 0 deletions intersect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,92 @@ func TestUnion(t *testing.T) {
is.IsType(nonempty, allStrings, "type preserved")
}

func TestUnionBy(t *testing.T) {
t.Parallel()
is := assert.New(t)

testFunc := func(i int) int {
return i / 2
}

result1 := UnionBy(testFunc, []int{0, 1, 2, 3, 4, 5}, []int{0, 2, 10})
result2 := UnionBy(testFunc, []int{0, 1, 2, 3, 4, 5}, []int{6, 7})
result3 := UnionBy(testFunc, []int{0, 1, 2, 3, 4, 5}, []int{})
result4 := UnionBy(testFunc, []int{0, 1, 2}, []int{0, 1, 2})
result5 := UnionBy(testFunc, []int{}, []int{})
is.Equal([]int{0, 2, 4, 10}, result1)
is.Equal([]int{0, 2, 4, 6}, result2)
is.Equal([]int{0, 2, 4}, result3)
is.Equal([]int{0, 2}, result4)
is.Equal([]int{}, result5)

result11 := UnionBy(testFunc, []int{0, 1, 2, 3, 4, 5}, []int{0, 2, 10}, []int{0, 1, 11})
result12 := UnionBy(testFunc, []int{0, 1, 2, 3, 4, 5}, []int{6, 7}, []int{8, 9})
result13 := UnionBy(testFunc, []int{0, 1, 2, 3, 4, 5}, []int{}, []int{})
result14 := UnionBy(testFunc, []int{0, 1, 2}, []int{0, 1, 2}, []int{0, 1, 2})
result15 := UnionBy(testFunc, []int{}, []int{}, []int{})
is.Equal([]int{0, 2, 4, 10}, result11)
is.Equal([]int{0, 2, 4, 6, 8}, result12)
is.Equal([]int{0, 2, 4}, result13)
is.Equal([]int{0, 2}, result14)
is.Equal([]int{}, result15)
}

func TestUnionByErr(t *testing.T) {
t.Parallel()
is := assert.New(t)

testFunc := func(i int) (int, error) {
return i / 2, nil
}

result1, err1 := UnionByErr(testFunc, []int{0, 1, 2, 3, 4, 5}, []int{0, 2, 10})
result2, err2 := UnionByErr(testFunc, []int{0, 1, 2, 3, 4, 5}, []int{6, 7})
result3, err3 := UnionByErr(testFunc, []int{0, 1, 2, 3, 4, 5}, []int{})
result4, err4 := UnionByErr(testFunc, []int{0, 1, 2}, []int{0, 1, 2})
result5, err5 := UnionByErr(testFunc, []int{}, []int{})
is.NoError(err1)
is.NoError(err2)
is.NoError(err3)
is.NoError(err4)
is.NoError(err5)
is.Equal([]int{0, 2, 4, 10}, result1)
is.Equal([]int{0, 2, 4, 6}, result2)
is.Equal([]int{0, 2, 4}, result3)
is.Equal([]int{0, 2}, result4)
is.Equal([]int{}, result5)

result11, err11 := UnionByErr(testFunc, []int{0, 1, 2, 3, 4, 5}, []int{0, 2, 10}, []int{0, 1, 11})
result12, err12 := UnionByErr(testFunc, []int{0, 1, 2, 3, 4, 5}, []int{6, 7}, []int{8, 9})
result13, err13 := UnionByErr(testFunc, []int{0, 1, 2, 3, 4, 5}, []int{}, []int{})
result14, err14 := UnionByErr(testFunc, []int{0, 1, 2}, []int{0, 1, 2}, []int{0, 1, 2})
result15, err15 := UnionByErr(testFunc, []int{}, []int{}, []int{})
is.NoError(err11)
is.NoError(err12)
is.NoError(err13)
is.NoError(err14)
is.NoError(err15)
is.Equal([]int{0, 2, 4, 10}, result11)
is.Equal([]int{0, 2, 4, 6, 8}, result12)
is.Equal([]int{0, 2, 4}, result13)
is.Equal([]int{0, 2}, result14)
is.Equal([]int{}, result15)

// Test error case
errFunc := func(i int) (int, error) {
if i == 2 {
return 0, assert.AnError
}
return i / 2, nil
}

_, err6 := UnionByErr(errFunc, []int{0, 1, 2, 3, 4, 5}, []int{0, 2, 10})
is.Error(err6)

_, err7 := UnionByErr(errFunc, []int{0, 1, 3, 4, 5}, []int{2, 10})
is.Error(err7)
}

func TestWithout(t *testing.T) {
t.Parallel()
is := assert.New(t)
Expand Down