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
45 changes: 41 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ Supported intersection helpers:
- [Intersect](#intersect)
- [IntersectBy](#intersectby)
- [Difference](#difference)
- [DifferenceBy](#differenceby)
- [Union](#union)
- [Without](#without)
- [WithoutBy](#withoutby)
Expand Down Expand Up @@ -3047,19 +3048,55 @@ result4 := lo.IntersectBy(transform, []int{0, 3, 5, 7}, []int{3, 5}, []int{0, 1,

Returns the difference between two collections.

- The first value is the collection of elements absent from list2.
- The second value is the collection of elements absent from list1.
- The first value is the collection of elements from `left` absent from `right`.
- The second value is the collection of elements from `right` absent from `left`.

```go
left, right := lo.Difference([]int{0, 1, 2, 3, 4, 5}, []int{0, 2, 6})
left := []int{0, 1, 2, 3, 4, 5}
right := []int{0, 2, 6}

notInRight, notInLeft := lo.Difference(left, right)
// []int{1, 3, 4, 5}, []int{6}

left, right := lo.Difference([]int{0, 1, 2, 3, 4, 5}, []int{0, 1, 2, 3, 4, 5})
left = []int{0, 1, 2, 3, 4, 5}
right = []int{0, 1, 2, 3, 4, 5}

notInRight, notInLeft = lo.Difference(left, right)
// []int{}, []int{}
```

[[play](https://go.dev/play/p/pKE-JgzqRpz)]

### DifferenceBy

Returns the difference between two collections using a custom key selector function.

- The first value is the collection of elements from `left` whose keys are absent from `right`.
- The second value is the collection of elements from `right` whose keys are absent from `left`.

```go
type User struct {
ID int
Name string
}

left := []User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
{ID: 3, Name: "Charlie"},
}

right := []User{
{ID: 2, Name: "Robert"},
{ID: 4, Name: "David"},
}

notInRight, notInLeft := lo.DifferenceBy(left, right, func(user User) int {
return user.ID
})
// []User{{ID: 1, Name: "Alice"}, {ID: 3, Name: "Charlie"}}, []User{{ID: 4, Name: "David"}}
```

### Union

Returns all distinct elements from given collections. Result will not change the order of elements relatively.
Expand Down
12 changes: 7 additions & 5 deletions docs/data/core-difference.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: Difference
slug: difference
sourceRef: intersect.go#L124
sourceRef: intersect.go#L210
category: core
subCategory: intersect
playUrl: https://go.dev/play/p/pKE-JgzqRpz
Expand All @@ -16,14 +16,16 @@ similarHelpers:
- core#slice#uniqby
position: 90
signatures:
- "func Difference[T comparable, Slice ~[]T](list1 Slice, list2 Slice) (Slice, Slice)"
- "func Difference[T comparable, Slice ~[]T](left, right Slice) (notInRight, notInLeft Slice)"
---

Returns the difference between two collections. The first slice contains elements absent from list2; the second contains elements absent from list1.
Returns the difference between two collections. The first slice contains elements from left absent from right; the second contains elements from right absent from left.

```go
left, right := lo.Difference([]int{0, 1, 2, 3, 4, 5}, []int{0, 2, 6})
left := []int{0, 1, 2, 3, 4, 5}
right := []int{0, 2, 6}

notInRight, notInLeft := lo.Difference(left, right)
// []int{1, 3, 4, 5}, []int{6}
```


42 changes: 42 additions & 0 deletions docs/data/core-differenceby.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
name: DifferenceBy
slug: differenceby
sourceRef: intersect.go#L235
category: core
subCategory: intersect
variantHelpers:
- core#intersect#differenceby
similarHelpers:
- core#intersect#difference
- core#intersect#intersectby
- core#intersect#withoutby
- core#slice#uniqby
position: 95
signatures:
- "func DifferenceBy[T any, R comparable, Slice ~[]T](left, right Slice, iteratee func(item T) R) (notInRight, notInLeft Slice)"
---

Returns the difference between two collections using a custom key selector function. The first slice contains elements from left whose keys are absent from right; the second contains elements from right whose keys are absent from left.

```go
type User struct {
ID int
Name string
}

left := []User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
{ID: 3, Name: "Charlie"},
}

right := []User{
{ID: 2, Name: "Robert"},
{ID: 4, Name: "David"},
}

notInRight, notInLeft := lo.DifferenceBy(left, right, func(user User) int {
return user.ID
})
// []User{{ID: 1, Name: "Alice"}, {ID: 3, Name: "Charlie"}}, []User{{ID: 4, Name: "David"}}
```
64 changes: 50 additions & 14 deletions intersect.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,29 +204,65 @@ func IntersectBy[T any, K comparable, Slice ~[]T](transform func(T) K, lists ...
}

// Difference returns the difference between two collections.
// The first value is the collection of elements absent from list2.
// The second value is the collection of elements absent from list1.
// The first value is the collection of elements from left absent from right.
// The second value is the collection of elements from right absent from left.
// Play: https://go.dev/play/p/pKE-JgzqRpz
func Difference[T comparable, Slice ~[]T](list1, list2 Slice) (Slice, Slice) {
left := make(Slice, 0, len(list1))
right := make(Slice, 0, len(list2))
func Difference[T comparable, Slice ~[]T](left, right Slice) (notInRight, notInLeft Slice) {
notInRight = make(Slice, 0, len(left))
notInLeft = make(Slice, 0, len(right))

seenLeft := Keyify(list1)
seenRight := Keyify(list2)
seenLeft := Keyify(left)
seenRight := Keyify(right)

for i := range list1 {
if _, ok := seenRight[list1[i]]; !ok {
left = append(left, list1[i])
for i := range left {
if _, ok := seenRight[left[i]]; !ok {
notInRight = append(notInRight, left[i])
}
}

for i := range list2 {
if _, ok := seenLeft[list2[i]]; !ok {
right = append(right, list2[i])
for i := range right {
if _, ok := seenLeft[right[i]]; !ok {
notInLeft = append(notInLeft, right[i])
}
}

return left, right
return notInRight, notInLeft
}

// DifferenceBy returns the difference between two collections using a custom key selector function.
// The first value is the collection of elements from left whose keys are absent from right.
// The second value is the collection of elements from right whose keys are absent from left.
func DifferenceBy[T any, R comparable, Slice ~[]T](left, right Slice, iteratee func(item T) R) (notInRight, notInLeft Slice) {
notInLeft = make(Slice, 0, len(right))
notInRight = make(Slice, 0, len(left))

seenLeft := make(map[R]struct{}, len(left))
inLeft := make([]R, len(left))
seenRight := make(map[R]struct{}, len(right))
inRight := make([]R, len(right))

for i := range left {
inLeft[i] = iteratee(left[i])
seenLeft[inLeft[i]] = struct{}{}
}
for i := range right {
inRight[i] = iteratee(right[i])
seenRight[inRight[i]] = struct{}{}
}

for i := range inLeft {
if _, ok := seenRight[inLeft[i]]; !ok {
notInRight = append(notInRight, left[i])
}
}

for i := range inRight {
if _, ok := seenLeft[inRight[i]]; !ok {
notInLeft = append(notInLeft, right[i])
}
}

return notInRight, notInLeft
}

// Union returns all distinct elements from given collections.
Expand Down
75 changes: 63 additions & 12 deletions intersect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,23 +255,74 @@ func TestDifference(t *testing.T) {
t.Parallel()
is := assert.New(t)

left1, right1 := Difference([]int{0, 1, 2, 3, 4, 5}, []int{0, 2, 6})
is.Equal([]int{1, 3, 4, 5}, left1)
is.Equal([]int{6}, right1)
notInRight1, notInLeft1 := Difference([]int{0, 1, 2, 3, 4, 5}, []int{0, 2, 6})
is.Equal([]int{1, 3, 4, 5}, notInRight1)
is.Equal([]int{6}, notInLeft1)

left2, right2 := Difference([]int{1, 2, 3, 4, 5}, []int{0, 6})
is.Equal([]int{1, 2, 3, 4, 5}, left2)
is.Equal([]int{0, 6}, right2)
notInRight2, notInLeft2 := Difference([]int{1, 2, 3, 4, 5}, []int{0, 6})
is.Equal([]int{1, 2, 3, 4, 5}, notInRight2)
is.Equal([]int{0, 6}, notInLeft2)

left3, right3 := Difference([]int{0, 1, 2, 3, 4, 5}, []int{0, 1, 2, 3, 4, 5})
is.Empty(left3)
is.Empty(right3)
notInRight3, notInLeft3 := Difference([]int{0, 1, 2, 3, 4, 5}, []int{0, 1, 2, 3, 4, 5})
is.Empty(notInRight3)
is.Empty(notInLeft3)

type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
a, b := Difference(allStrings, allStrings)
is.IsType(a, allStrings, "type preserved")
is.IsType(b, allStrings, "type preserved")
notInRight4, notInLeft4 := Difference(allStrings, allStrings)
is.IsType(notInRight4, allStrings, "type preserved")
is.IsType(notInLeft4, allStrings, "type preserved")
}

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

type User struct {
ID int
Name string
}

left := []User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
{ID: 3, Name: "Charlie"},
}

right := []User{
{ID: 2, Name: "Robert"},
{ID: 4, Name: "David"},
}

notInRight1, notInLeft1 := DifferenceBy(left, right, func(u User) int {
return u.ID
})
is.Equal([]User{{ID: 1, Name: "Alice"}, {ID: 3, Name: "Charlie"}}, notInRight1)
is.Equal([]User{{ID: 4, Name: "David"}}, notInLeft1)

notInRight2, notInLeft2 := DifferenceBy(
[]User{{ID: 1, Name: "Alice"}, {ID: 1, Name: "Alicia"}, {ID: 2, Name: "Bob"}},
[]User{{ID: 2, Name: "Bobby"}, {ID: 3, Name: "Charlie"}, {ID: 3, Name: "Charlotte"}},
func(u User) int {
return u.ID
},
)
is.Equal([]User{{ID: 1, Name: "Alice"}, {ID: 1, Name: "Alicia"}}, notInRight2)
is.Equal([]User{{ID: 3, Name: "Charlie"}, {ID: 3, Name: "Charlotte"}}, notInLeft2)

notInRight3, notInLeft3 := DifferenceBy(left, left, func(u User) string {
return u.Name
})
is.Empty(notInRight3)
is.Empty(notInLeft3)

type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
notInRight4, notInLeft4 := DifferenceBy(allStrings, allStrings, func(s string) string {
return s
})
is.IsType(notInRight4, allStrings, "type preserved")
is.IsType(notInLeft4, allStrings, "type preserved")
}

func TestUnion(t *testing.T) {
Expand Down
24 changes: 24 additions & 0 deletions lo_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4149,3 +4149,27 @@ func ExampleIntersectBy() {
// Output:
// [0]
}

func ExampleDifferenceBy() {
type User struct {
ID int
Name string
}

left := []User{{ID: 1, Name: "Alice"}, {ID: 2, Name: "Bob"}, {ID: 3, Name: "Charlie"}}
right := []User{{ID: 2, Name: "Robert"}, {ID: 4, Name: "David"}}

notInRight, notInLeft := DifferenceBy(
left,
right,
func(user User) int {
return user.ID
},
)

fmt.Printf("%v\n", notInRight)
fmt.Printf("%v", notInLeft)
// Output:
// [{1 Alice} {3 Charlie}]
// [{4 David}]
}
Loading