mirror of
https://github.com/samber/lo.git
synced 2025-12-24 12:48:02 +08:00
Refactoring lo.IntersectBy + adding loit.IntersectBy + adding doc (#739)
* feat(intersectby): form transform callback in first position and add support for vaarg * feat(it): adding loit.IntersectBy * doc: adding lo.IntersectBy + loit.IntersectBy * doc: adding lo.IntersectBy + loit.IntersectBy * style: fix linter * doc: adding example for lo.IntersectBy
This commit is contained in:
24
README.md
24
README.md
@@ -246,6 +246,7 @@ Supported intersection helpers:
|
||||
- [None](#none)
|
||||
- [NoneBy](#noneby)
|
||||
- [Intersect](#intersect)
|
||||
- [IntersectBy](#intersectby)
|
||||
- [Difference](#difference)
|
||||
- [Union](#union)
|
||||
- [Without](#without)
|
||||
@@ -2597,11 +2598,32 @@ result2 := lo.Intersect([]int{0, 1, 2, 3, 4, 5}, []int{0, 6})
|
||||
result3 := lo.Intersect([]int{0, 1, 2, 3, 4, 5}, []int{-1, 6})
|
||||
// []int{}
|
||||
|
||||
|
||||
result4 := lo.Intersect([]int{0, 3, 5, 7}, []int{3, 5}, []int{0, 1, 2, 0, 3, 0})
|
||||
// []int{3}
|
||||
```
|
||||
|
||||
### IntersectBy
|
||||
|
||||
Returns the intersection between two collections using a custom key selector function.
|
||||
|
||||
```go
|
||||
transform := func(v int) string {
|
||||
return strconv.Itoa(v)
|
||||
}
|
||||
|
||||
result1 := lo.IntersectBy(transform, []int{0, 1, 2, 3, 4, 5}, []int{0, 2})
|
||||
// []int{0, 2}
|
||||
|
||||
result2 := lo.IntersectBy(transform, []int{0, 1, 2, 3, 4, 5}, []int{0, 6})
|
||||
// []int{0}
|
||||
|
||||
result3 := lo.IntersectBy(transform, []int{0, 1, 2, 3, 4, 5}, []int{-1, 6})
|
||||
// []int{}
|
||||
|
||||
result4 := lo.IntersectBy(transform, []int{0, 3, 5, 7}, []int{3, 5}, []int{0, 1, 2, 0, 3, 0})
|
||||
// []int{3}
|
||||
```
|
||||
|
||||
### Difference
|
||||
|
||||
Returns the difference between two collections.
|
||||
|
||||
@@ -8,6 +8,9 @@ playUrl: https://go.dev/play/p/uuElL9X9e58
|
||||
variantHelpers:
|
||||
- core#intersect#intersect
|
||||
similarHelpers:
|
||||
- core#intersect#intersectby
|
||||
- it#intersect#intersect
|
||||
- it#intersect#intersectby
|
||||
- core#intersect#difference
|
||||
- core#intersect#union
|
||||
- core#intersect#without
|
||||
@@ -20,8 +23,6 @@ signatures:
|
||||
Returns the intersection between collections.
|
||||
|
||||
```go
|
||||
lo.Intersect([]int{0, 3, 5, 7}, []int{3, 5}, []int{0, 1, 2, 0, 3, 0}))
|
||||
lo.Intersect([]int{0, 3, 5, 7}, []int{3, 5}, []int{0, 1, 2, 0, 3, 0})
|
||||
// []int{3}
|
||||
```
|
||||
|
||||
|
||||
|
||||
32
docs/data/core-intersectby.md
Normal file
32
docs/data/core-intersectby.md
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
name: IntersectBy
|
||||
slug: intersectby
|
||||
sourceRef: intersect.go#L174
|
||||
category: core
|
||||
subCategory: intersect
|
||||
playUrl:
|
||||
variantHelpers:
|
||||
- core#intersect#intersectby
|
||||
similarHelpers:
|
||||
- core#intersect#intersect
|
||||
- it#intersect#intersect
|
||||
- it#intersect#intersectby
|
||||
- core#intersect#difference
|
||||
- core#intersect#union
|
||||
- core#intersect#without
|
||||
- core#slice#uniq
|
||||
position: 80
|
||||
signatures:
|
||||
- "func IntersectBy[T any, K comparable, Slice ~[]T](transform func(T) K, lists ...Slice) Slice"
|
||||
---
|
||||
|
||||
Returns the intersection between two collections using a custom key selector function.
|
||||
|
||||
```go
|
||||
transform := func(v int) string {
|
||||
return strconv.Itoa(v)
|
||||
}
|
||||
|
||||
lo.IntersectBy(transform, []int{0, 3, 5, 7}, []int{3, 5}, []int{0, 1, 2, 0, 3, 0})
|
||||
// []int{3}
|
||||
```
|
||||
@@ -10,7 +10,9 @@ playUrl: "https://go.dev/play/p/kz3cGhGZZWF"
|
||||
variantHelpers:
|
||||
- it#intersect#intersect
|
||||
similarHelpers:
|
||||
- it#intersect#intersectby
|
||||
- core#slice#intersect
|
||||
- core#slice#intersectby
|
||||
- it#intersect#union
|
||||
position: 10
|
||||
---
|
||||
|
||||
53
docs/data/it-intersectby.md
Normal file
53
docs/data/it-intersectby.md
Normal file
@@ -0,0 +1,53 @@
|
||||
---
|
||||
name: IntersectBy
|
||||
slug: intersectby
|
||||
sourceRef: it/intersect.go#L78
|
||||
category: it
|
||||
subCategory: intersect
|
||||
signatures:
|
||||
- "func IntersectBy[T any, K comparable, I ~func(func(T) bool)](func(T) K, lists ...I) I"
|
||||
playUrl:
|
||||
variantHelpers:
|
||||
- it#intersect#intersectby
|
||||
similarHelpers:
|
||||
- it#intersect#intersect
|
||||
- core#slice#intersect
|
||||
- core#slice#intersectby
|
||||
- it#intersect#union
|
||||
position: 10
|
||||
---
|
||||
|
||||
Returns the intersection between given collections using a custom key selector function.
|
||||
|
||||
Examples:
|
||||
|
||||
```go
|
||||
seq1 := func(yield func(int) bool) {
|
||||
_ = yield(1)
|
||||
_ = yield(2)
|
||||
_ = yield(3)
|
||||
_ = yield(4)
|
||||
}
|
||||
seq2 := func(yield func(int) bool) {
|
||||
_ = yield(2)
|
||||
_ = yield(3)
|
||||
_ = yield(5)
|
||||
}
|
||||
seq3 := func(yield func(int) bool) {
|
||||
_ = yield(3)
|
||||
_ = yield(2)
|
||||
_ = yield(6)
|
||||
}
|
||||
|
||||
transform := func(v int) string {
|
||||
return strconv.Itoa(v)
|
||||
}
|
||||
|
||||
intersection := it.IntersectBy(transform, seq1, seq2, seq3)
|
||||
|
||||
var result []int
|
||||
for v := range intersection {
|
||||
result = append(result, v)
|
||||
}
|
||||
// result contains 2, 3 (elements present in all sequences)
|
||||
```
|
||||
@@ -51,3 +51,7 @@ Please add an example of your helper in the file named `xxxx_example_test.go`. I
|
||||
1- If a callback returns a single bool then it should probably be called "predicate".
|
||||
2- If a callback is used to change a collection element into something else then it should probably be called "transform".
|
||||
3- If a callback returns nothing (void) then it should probably be called "callback".
|
||||
|
||||
### Types
|
||||
|
||||
1- Generic functions must preserve the underlying type of collections so that the returned values maintain the same type as the input. See [#365](https://github.com/samber/lo/pull/365/files).
|
||||
|
||||
2
docs/static/llms.txt
vendored
2
docs/static/llms.txt
vendored
@@ -137,6 +137,7 @@ Lo is built on a foundation of pragmatic engineering principles that balance pow
|
||||
- None: Check if no elements in collection match subset
|
||||
- NoneBy: Check if no elements match predicate
|
||||
- Intersect: Get elements common to all collections
|
||||
- IntersectBy: Get elements common to all collections with key selector
|
||||
- Difference: Get elements in first collection but not in others
|
||||
- Union: Get all unique elements from collections
|
||||
- Without: Get collection with specified elements removed
|
||||
@@ -404,6 +405,7 @@ The lo/it package provides iterator helpers for lazy evaluation and streaming op
|
||||
- None: Check if no elements in sequence match subset
|
||||
- NoneBy: Check if no elements match predicate
|
||||
- Intersect: Get elements common to all sequences
|
||||
- IntersectBy: Get elements common to all sequences with key selector
|
||||
- Union: Get all unique elements from sequences
|
||||
- Without: Get sequence with specified elements excluded
|
||||
- WithoutBy: Get sequence with elements excluded by key transform
|
||||
|
||||
63
intersect.go
63
intersect.go
@@ -171,24 +171,61 @@ func Intersect[T comparable, Slice ~[]T](lists ...Slice) Slice {
|
||||
}
|
||||
|
||||
// IntersectBy returns the intersection between two collections using a custom key selector function.
|
||||
// It preserves the order of elements from the second list (list2).
|
||||
func IntersectBy[T any, K comparable, Slice ~[]T](list1 Slice, list2 Slice, iteratee func(T) K) Slice {
|
||||
result := make(Slice, 0)
|
||||
seen := make(map[K]struct{})
|
||||
|
||||
for _, item := range list1 {
|
||||
key := iteratee(item)
|
||||
seen[key] = struct{}{}
|
||||
func IntersectBy[T any, K comparable, Slice ~[]T](transform func(T) K, lists ...Slice) Slice {
|
||||
if len(lists) == 0 {
|
||||
return Slice{}
|
||||
}
|
||||
|
||||
for _, item := range list2 {
|
||||
key := iteratee(item)
|
||||
if _, exists := seen[key]; exists {
|
||||
result = append(result, item)
|
||||
if len(lists) == 1 {
|
||||
return lists[0]
|
||||
}
|
||||
|
||||
seen := make(map[K]bool)
|
||||
|
||||
for i := len(lists) - 1; i >= 0; i-- {
|
||||
if i == len(lists)-1 {
|
||||
for _, item := range lists[i] {
|
||||
k := transform(item)
|
||||
seen[k] = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
result := make(Slice, 0, len(seen))
|
||||
for _, item := range lists[0] {
|
||||
k := transform(item)
|
||||
if _, ok := seen[k]; ok {
|
||||
result = append(result, item)
|
||||
delete(seen, k)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
for k := range seen {
|
||||
seen[k] = false
|
||||
}
|
||||
|
||||
for _, item := range lists[i] {
|
||||
k := transform(item)
|
||||
if _, ok := seen[k]; ok {
|
||||
seen[k] = true
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range seen {
|
||||
if !v {
|
||||
delete(seen, k)
|
||||
}
|
||||
}
|
||||
|
||||
if len(seen) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
return Slice{}
|
||||
}
|
||||
|
||||
// Difference returns the difference between two collections.
|
||||
|
||||
@@ -226,23 +226,23 @@ func TestIntersectBy(t *testing.T) {
|
||||
{ID: 4, Name: "Alice"},
|
||||
}
|
||||
|
||||
intersectByID := IntersectBy(list1, list2, func(u User) int {
|
||||
intersectByID := IntersectBy(func(u User) int {
|
||||
return u.ID
|
||||
})
|
||||
is.Equal(intersectByID, []User{{ID: 2, Name: "Robert"}, {ID: 3, Name: "Charlie"}})
|
||||
// output: [{2 Robert} {3 Charlie}]
|
||||
}, list1, list2)
|
||||
is.ElementsMatch(intersectByID, []User{{ID: 2, Name: "Bob"}, {ID: 3, Name: "Charlie"}})
|
||||
|
||||
intersectByName := IntersectBy(list1, list2, func(u User) string {
|
||||
intersectByName := IntersectBy(func(u User) string {
|
||||
return u.Name
|
||||
})
|
||||
is.Equal(intersectByName, []User{{ID: 3, Name: "Charlie"}, {ID: 4, Name: "Alice"}})
|
||||
// output: [{3 Charlie} {4 Alice}]
|
||||
}, list1, list2)
|
||||
is.ElementsMatch(intersectByName, []User{{ID: 3, Name: "Charlie"}, {ID: 1, Name: "Alice"}})
|
||||
|
||||
intersectByIDAndName := IntersectBy(list1, list2, func(u User) string {
|
||||
intersectByIDAndName := IntersectBy(func(u User) string {
|
||||
return strconv.Itoa(u.ID) + u.Name
|
||||
})
|
||||
is.Equal(intersectByIDAndName, []User{{ID: 3, Name: "Charlie"}})
|
||||
// output: [{3 Charlie}]
|
||||
}, list1, list2)
|
||||
is.ElementsMatch(intersectByIDAndName, []User{{ID: 3, Name: "Charlie"}})
|
||||
|
||||
result := IntersectBy(strconv.Itoa, []int{0, 6, 0, 3}, []int{0, 1, 2, 3, 4, 5}, []int{0, 6})
|
||||
is.ElementsMatch(result, []int{0})
|
||||
}
|
||||
|
||||
func TestDifference(t *testing.T) {
|
||||
|
||||
@@ -151,6 +151,68 @@ func Intersect[T comparable, I ~func(func(T) bool)](lists ...I) I { //nolint:goc
|
||||
}
|
||||
}
|
||||
|
||||
// IntersectBy returns the intersection between given collections using a
|
||||
// custom key selector function.
|
||||
// Will allocate a map large enough to hold all distinct elements.
|
||||
// Long heterogeneous input sequences can cause excessive memory usage.
|
||||
func IntersectBy[T any, K comparable, I ~func(func(T) bool)](transform func(T) K, lists ...I) I { //nolint:gocyclo
|
||||
if len(lists) == 0 {
|
||||
return I(Empty[T]())
|
||||
}
|
||||
|
||||
if len(lists) == 1 {
|
||||
return lists[0]
|
||||
}
|
||||
|
||||
return func(yield func(T) bool) {
|
||||
seen := make(map[K]bool)
|
||||
|
||||
for i := len(lists) - 1; i >= 0; i-- {
|
||||
if i == len(lists)-1 {
|
||||
for item := range lists[i] {
|
||||
k := transform(item)
|
||||
seen[k] = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
for item := range lists[0] {
|
||||
k := transform(item)
|
||||
if _, ok := seen[k]; ok {
|
||||
if !yield(item) {
|
||||
return
|
||||
}
|
||||
delete(seen, k)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
for k := range seen {
|
||||
seen[k] = false
|
||||
}
|
||||
|
||||
for item := range lists[i] {
|
||||
k := transform(item)
|
||||
if _, ok := seen[k]; ok {
|
||||
seen[k] = true
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range seen {
|
||||
if !v {
|
||||
delete(seen, k)
|
||||
}
|
||||
}
|
||||
|
||||
if len(seen) == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Union returns all distinct elements from given collections.
|
||||
// Will allocate a map large enough to hold all distinct elements.
|
||||
// Long heterogeneous input sequences can cause excessive memory usage.
|
||||
|
||||
@@ -5,6 +5,7 @@ package it
|
||||
import (
|
||||
"iter"
|
||||
"slices"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -206,6 +207,38 @@ func TestIntersect(t *testing.T) {
|
||||
is.IsType(nonempty, allStrings, "type preserved")
|
||||
}
|
||||
|
||||
func TestIntersectBy(t *testing.T) {
|
||||
t.Parallel()
|
||||
is := assert.New(t)
|
||||
|
||||
transform := strconv.Itoa
|
||||
|
||||
result1 := IntersectBy(transform, []iter.Seq[int]{}...)
|
||||
result2 := IntersectBy(transform, values(0, 1, 2, 3, 4, 5))
|
||||
result3 := IntersectBy(transform, values(0, 1, 2, 3, 4, 5), values(0, 6))
|
||||
result4 := IntersectBy(transform, values(0, 1, 2, 3, 4, 5), values(-1, 6))
|
||||
result5 := IntersectBy(transform, values(0, 6, 0), values(0, 1, 2, 3, 4, 5))
|
||||
result6 := IntersectBy(transform, values(0, 1, 2, 3, 4, 5), values(0, 6, 0))
|
||||
result7 := IntersectBy(transform, values(0, 1, 2), values(1, 2, 3), values(2, 3, 4))
|
||||
result8 := IntersectBy(transform, values(0, 1, 2), values(1, 2, 3), values(2, 3, 4), values(3, 4, 5))
|
||||
result9 := IntersectBy(transform, values(0, 1, 2), values(0, 1, 2), values(1, 2, 3), values(2, 3, 4), values(3, 4, 5))
|
||||
|
||||
is.Empty(slices.Collect(result1))
|
||||
is.Equal([]int{0, 1, 2, 3, 4, 5}, slices.Collect(result2))
|
||||
is.Equal([]int{0}, slices.Collect(result3))
|
||||
is.Empty(slices.Collect(result4))
|
||||
is.Equal([]int{0}, slices.Collect(result5))
|
||||
is.Equal([]int{0}, slices.Collect(result6))
|
||||
is.Equal([]int{2}, slices.Collect(result7))
|
||||
is.Empty(slices.Collect(result8))
|
||||
is.Empty(slices.Collect(result9))
|
||||
|
||||
type myStrings iter.Seq[string]
|
||||
allStrings := myStrings(values("", "foo", "bar"))
|
||||
nonempty := IntersectBy(func(s string) string { return s + s }, allStrings, allStrings)
|
||||
is.IsType(nonempty, allStrings, "type preserved")
|
||||
}
|
||||
|
||||
func TestUnion(t *testing.T) {
|
||||
t.Parallel()
|
||||
is := assert.New(t)
|
||||
|
||||
@@ -3343,7 +3343,15 @@ func ExampleCrossJoinBy9() {
|
||||
}
|
||||
|
||||
func ExampleIntersect() {
|
||||
fmt.Printf("%v", Intersect([]int{0, 3, 5, 7}, []int{3, 5}, []int{0, 1, 2, 0, 3, 0}))
|
||||
result := Intersect([]int{0, 3, 5, 7}, []int{3, 5}, []int{0, 1, 2, 0, 3, 0})
|
||||
fmt.Printf("%v", result)
|
||||
// Output:
|
||||
// [3]
|
||||
}
|
||||
|
||||
func ExampleIntersectBy() {
|
||||
result := IntersectBy(strconv.Itoa, []int{0, 6, 0, 3}, []int{0, 1, 2, 3, 4, 5}, []int{0, 6})
|
||||
fmt.Printf("%v", result)
|
||||
// Output:
|
||||
// [0]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user