Skip to content

Commit a932f13

Browse files
authored
APP-467-Add support for Left, Right, and Full Outer Joins (#2)
1 parent c27138a commit a932f13

File tree

2 files changed

+510
-20
lines changed

2 files changed

+510
-20
lines changed

joining.go

Lines changed: 117 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
11
package weaklinq
22

3-
import "reflect"
4-
53
//----------------------------------------------------------------------------//
64
// Joining //
75
//----------------------------------------------------------------------------//
86

97
////////////////////////////////////////////////////////////////////////////////
108

9+
// joinType is an enum that represents the type of join to perform.
10+
type joinType int
11+
12+
const (
13+
InnerJoin joinType = iota
14+
LeftJoin
15+
RightJoin
16+
FullOuterJoin
17+
)
18+
19+
////////////////////////////////////////////////////////////////////////////////
20+
1121
// JoinIterable is a specialized iterable that includes a right iterable, a key
1222
// selector for the left iterable, and a key selector for the right iterable.
1323
// These selectors are stored until the collection is iterated, and then
@@ -17,6 +27,7 @@ type JoinIterable[T any] struct {
1727
rightIterable Iterable[any]
1828
keySelector func(T) any
1929
rightKeySelector func(any) any
30+
joinType joinType
2031
}
2132

2233
/////////////////////////////////////////////////////////////////////////////////
@@ -43,6 +54,7 @@ func defaultJoinIterable[T any](iterable Iterable[T], joinIterable Iterable[any]
4354
rightIterable: joinIterable,
4455
keySelector: identitySelector[T],
4556
rightKeySelector: identitySelector[any],
57+
joinType: InnerJoin,
4658
}
4759
}
4860

@@ -61,25 +73,43 @@ func (iterable Iterable[T]) Join(joinIterable Iterable[any]) DeferredJoinIterabl
6173

6274
////////////////////////////////////////////////////////////////////////////////
6375

64-
// JoinSlice returns a new DeferredJoinIterable that will join the items of the given slice
65-
func (iterable Iterable[T]) JoinSlice(joinSlice any) DeferredJoinIterable[T] {
76+
// LeftJoin returns a new DeferredJoinIterable that will perform a left join
77+
// on the given iterable.
78+
func (iterable Iterable[T]) LeftJoin(joinIterable Iterable[any]) DeferredJoinIterable[T] {
6679

67-
if reflect.TypeOf(joinSlice).Kind() != reflect.Slice {
68-
panic("'joinSlice' must be a slice")
69-
}
80+
defaultIterable := defaultJoinIterable(iterable, joinIterable)
81+
defaultIterable.joinType = LeftJoin
82+
return DeferredJoinIterable[T](defaultIterable)
83+
}
7084

71-
joinIterable := Iterable[any]{
72-
Seq: func(yield func(any) bool) {
73-
s := reflect.ValueOf(joinSlice)
74-
for i := 0; i < s.Len(); i++ {
75-
if !yield(s.Index(i).Interface()) {
76-
return
77-
}
78-
}
79-
},
80-
}
85+
////////////////////////////////////////////////////////////////////////////////
86+
87+
// RightJoin returns a new DeferredJoinIterable that will perform a right join
88+
// on the given iterable.
89+
func (iterable Iterable[T]) RightJoin(joinIterable Iterable[any]) DeferredJoinIterable[T] {
8190

82-
return iterable.Join(joinIterable)
91+
defaultIterable := defaultJoinIterable(iterable, joinIterable)
92+
defaultIterable.joinType = RightJoin
93+
return DeferredJoinIterable[T](defaultIterable)
94+
}
95+
96+
////////////////////////////////////////////////////////////////////////////////
97+
98+
// FullOuterJoin returns a new DeferredJoinIterable that will perform a full
99+
// outer join on the given iterable.
100+
func (iterable Iterable[T]) FullOuterJoin(joinIterable Iterable[any]) DeferredJoinIterable[T] {
101+
102+
defaultIterable := defaultJoinIterable(iterable, joinIterable)
103+
defaultIterable.joinType = FullOuterJoin
104+
return DeferredJoinIterable[T](defaultIterable)
105+
}
106+
107+
////////////////////////////////////////////////////////////////////////////////
108+
109+
// JoinSlice returns a new DeferredJoinIterable that will join the items of the given slice
110+
func (iterable Iterable[T]) JoinSlice(joinSlice []T) DeferredJoinIterable[T] {
111+
112+
return iterable.Join(From(joinSlice).AsAny())
83113

84114
/*
85115
linq.From([]T{...}).
@@ -89,6 +119,46 @@ func (iterable Iterable[T]) JoinSlice(joinSlice any) DeferredJoinIterable[T] {
89119

90120
////////////////////////////////////////////////////////////////////////////////
91121

122+
// LeftJoinSlice returns a new DeferredJoinIterable that will perform a left
123+
// join on the given slice.
124+
func (iterable Iterable[T]) LeftJoinSlice(joinSlice []T) DeferredJoinIterable[T] {
125+
126+
return iterable.LeftJoin(From(joinSlice).AsAny())
127+
128+
/*
129+
linq.From([]T{...}).
130+
LeftJoinSlice([]TRight{...})
131+
*/
132+
}
133+
134+
// RightJoinSlice returns a new DeferredJoinIterable that will perform a right
135+
// join on the given slice.
136+
func (iterable Iterable[T]) RightJoinSlice(joinSlice []T) DeferredJoinIterable[T] {
137+
138+
return iterable.RightJoin(From(joinSlice).AsAny())
139+
140+
/*
141+
linq.From([]T{...}).
142+
RightJoinSlice([]TRight{...})
143+
*/
144+
}
145+
146+
////////////////////////////////////////////////////////////////////////////////
147+
148+
// FullOuterJoinSlice returns a new DeferredJoinIterable that will perform a
149+
// full outer join on the given slice.
150+
func (iterable Iterable[T]) FullOuterJoinSlice(joinSlice []T) DeferredJoinIterable[T] {
151+
152+
return iterable.FullOuterJoin(From(joinSlice).AsAny())
153+
154+
/*
155+
linq.From([]T{...}).
156+
FullOuterJoinSlice([]TRight{...})
157+
*/
158+
}
159+
160+
////////////////////////////////////////////////////////////////////////////////
161+
92162
// OnThis sets the key selector functions for both left and right iterables.
93163
func (iterable DeferredJoinIterable[T]) OnThis(keySelector func(T) any) DeferredJoinIterable[T] {
94164

@@ -178,21 +248,48 @@ func (iterable DeferredJoinIterable[T]) AsThis(joinSelector func(T, any) any) It
178248

179249
return Iterable[any]{
180250
Seq: func(yield func(any) bool) {
181-
iterable.itemIterable.Seq(func(leftItem T) bool {
251+
matchedRightItems := make(map[any]bool)
182252

253+
// Process left items
254+
iterable.itemIterable.Seq(func(leftItem T) bool {
183255
leftKey := iterable.keySelector(leftItem)
256+
rightItems, hasMatch := rightKeysToRightItems[leftKey]
184257

185-
if rightItems, ok := rightKeysToRightItems[leftKey]; ok {
258+
if hasMatch {
259+
// Inner, Left, Right, or Full Join with matches
186260
for _, rightItem := range rightItems {
261+
matchedRightItems[rightItem] = true
187262
result := joinSelector(leftItem, rightItem)
188263
if !yield(result) {
189264
return false
190265
}
191266
}
267+
} else if iterable.joinType == LeftJoin || iterable.joinType == FullOuterJoin {
268+
// Left or Full Join with no match - yield left with nil right
269+
result := joinSelector(leftItem, nil)
270+
if !yield(result) {
271+
return false
272+
}
192273
}
193274

194275
return true
195276
})
277+
278+
// Handle unmatched right items for Right and Full Join
279+
if iterable.joinType == RightJoin || iterable.joinType == FullOuterJoin {
280+
for _, rightItems := range rightKeysToRightItems {
281+
for _, rightItem := range rightItems {
282+
if !matchedRightItems[rightItem] {
283+
// Yield nil left with unmatched right
284+
var zeroLeft T
285+
result := joinSelector(zeroLeft, rightItem)
286+
if !yield(result) {
287+
return
288+
}
289+
}
290+
}
291+
}
292+
}
196293
},
197294
}
198295

0 commit comments

Comments
 (0)