Skip to content

Commit 4b14968

Browse files
donnolliujiandong03
authored andcommitted
feat: Add NestJoin and LeftJoin for slices
1 parent ea33bac commit 4b14968

3 files changed

Lines changed: 470 additions & 0 deletions

File tree

README.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,8 @@ Supported helpers for tuples:
216216
- [UnzipBy2 -> UnzipBy9](#unzipby2---unzipby9)
217217
- [CrossJoin2 -> CrossJoin2](#crossjoin2---crossjoin9)
218218
- [CrossJoinBy2 -> CrossJoinBy2](#crossjoinby2---crossjoinby9)
219+
- [NestJoin](#NestJoin)
220+
- [LeftJoin](#LeftJoin)
219221

220222
Supported helpers for time and duration:
221223

@@ -2607,6 +2609,97 @@ result, err := lo.CrossJoinByErr2([]string{"hello", "john"}, []int{1, 2}, func(a
26072609
// []string(nil), error("john not allowed")
26082610
```
26092611

2612+
### NestJoin
2613+
2614+
```go
2615+
type User struct {
2616+
Id uint64
2617+
Name string
2618+
}
2619+
2620+
type Book struct {
2621+
Id uint64
2622+
Title string
2623+
Author uint64 // = User.Id
2624+
}
2625+
2626+
type BookWithUser struct {
2627+
Book
2628+
UserName string
2629+
}
2630+
2631+
user1 := User{
2632+
Id: 1,
2633+
Name: "tt",
2634+
}
2635+
book1 := Book{
2636+
Id: 1,
2637+
Title: "Watcher from mikey",
2638+
Author: 1,
2639+
}
2640+
UserBookMatcher := func(j User, k Book) bool {
2641+
return j.Id == k.Author
2642+
}
2643+
2644+
result := NestJoin([]User{user1}, []Book{book1}, UserBookMatcher, func(j User, k Book) BookWithUser {
2645+
return BookWithUser{
2646+
Book: k,
2647+
UserName: j.Name,
2648+
}
2649+
})
2650+
fmt.Printf("%v", result)
2651+
// Output: [{{1 Watcher from mikey 1} tt}]
2652+
```
2653+
2654+
### LeftJoin
2655+
2656+
```go
2657+
type User struct {
2658+
Id uint64
2659+
Name string
2660+
}
2661+
2662+
type Book struct {
2663+
Id uint64
2664+
Title string
2665+
Author uint64 // = User.Id
2666+
}
2667+
2668+
type BookWithUser struct {
2669+
Book
2670+
UserName string
2671+
}
2672+
2673+
user1 := User{
2674+
Id: 1,
2675+
Name: "tt",
2676+
}
2677+
user2 := User{
2678+
Id: 2,
2679+
Name: "t2",
2680+
}
2681+
book1 := Book{
2682+
Id: 1,
2683+
Title: "Watcher from mikey",
2684+
Author: 1,
2685+
}
2686+
lk := func(item User) uint64 {
2687+
return item.Id
2688+
}
2689+
rk := func(item Book) uint64 {
2690+
return item.Author
2691+
}
2692+
2693+
result := LeftJoin([]User{user1, user2}, []Book{book1}, lk, rk, func(j User, k Book) BookWithUser {
2694+
return BookWithUser{
2695+
Book: k,
2696+
UserName: j.Name,
2697+
}
2698+
})
2699+
fmt.Printf("%v", result)
2700+
// Output: [{{1 Watcher from mikey 1} tt} {{0 0} t2}]
2701+
```
2702+
26102703
### Duration
26112704

26122705
Returns the time taken to execute a function.

tuples.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1870,3 +1870,76 @@ func CrossJoinByErr9[A, B, C, D, E, F, G, H, I, Out any](listA []A, listB []B, l
18701870

18711871
return result, nil
18721872
}
1873+
1874+
// NestJoin executes a condition join (Theta Join) between two slices using a
1875+
// Nested Loop Join algorithm.
1876+
//
1877+
// It iterates through every element in the 'left' slice and compares it with
1878+
// every element in the 'right' slice using the provided 'match' predicate.
1879+
// If 'match' returns true, the 'mapper' function is invoked to combine the two
1880+
// elements into a single result of type 'R'.
1881+
//
1882+
// Since it evaluates all possible pairs, it supports arbitrary matching criteria
1883+
// (e.g., inequalities, regex, or complex logical conditions).
1884+
//
1885+
// Time Complexity: O(N * M), where N is len(left) and M is len(right).
1886+
// Space Complexity: O(1) beyond the memory required for the resulting slice.
1887+
func NestJoin[J, K, R any](
1888+
left []J,
1889+
right []K,
1890+
match func(J, K) bool,
1891+
mapper func(J, K) R,
1892+
) []R {
1893+
var r []R
1894+
1895+
for _, j := range left {
1896+
for _, k := range right {
1897+
if !match(j, k) {
1898+
continue
1899+
}
1900+
r = append(r, mapper(j, k))
1901+
}
1902+
}
1903+
1904+
return r
1905+
}
1906+
1907+
// LeftJoin performs a left outer join between two slices based on a common key.
1908+
//
1909+
// It builds a lookup table (map) from the 'right' slice using 'rk' to extract keys.
1910+
// Then, it iterates through the 'left' slice, retrieves the corresponding element
1911+
// from the map using the key extracted by 'lk', and projects the result using 'mapper'.
1912+
//
1913+
// Behavior Notes:
1914+
// - If multiple elements in 'right' share the same key, 'KeyBy' will overwrite
1915+
// previous values, keeping only the last one.
1916+
// - If a key from the 'left' slice does not exist in 'right', the 'mapper'
1917+
// is still called, passing the zero value of type 'RE' for the right side.
1918+
// - The length of the returned slice is always equal to len(left).
1919+
//
1920+
// Time Complexity: O(N + M), where N is len(left) and M is len(right), leveraging
1921+
// hash-based lookup for optimal performance with large datasets.
1922+
// Space Complexity: O(M) to store the intermediate lookup map for the right slice.
1923+
func LeftJoin[K comparable, LE, RE, R any](
1924+
left []LE,
1925+
right []RE,
1926+
lk func(item LE) K,
1927+
rk func(item RE) K,
1928+
mapper func(LE, RE) R,
1929+
) []R {
1930+
if len(left) == 0 {
1931+
return nil
1932+
}
1933+
1934+
var r = make([]R, 0, len(left))
1935+
1936+
rm := KeyBy(right, rk)
1937+
1938+
for _, j := range left {
1939+
k := lk(j)
1940+
re := rm[k]
1941+
r = append(r, mapper(j, re))
1942+
}
1943+
1944+
return r
1945+
}

0 commit comments

Comments
 (0)