Skip to content
This repository was archived by the owner on Dec 28, 2023. It is now read-only.

Commit 64d5e71

Browse files
committed
Implement Enumerable::outerJoin()
1 parent 7b0a5c6 commit 64d5e71

File tree

8 files changed

+376
-5
lines changed

8 files changed

+376
-5
lines changed

src/Enumerable.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,21 @@ public function join($inner, $outerKeySelector, $innerKeySelector, callable $res
227227
return $this->newCollection($this->getProvider()->join($outer, $inner, $outerKeySelector, $innerKeySelector, $resultValueSelector));
228228
}
229229

230+
/**
231+
* @param array|\Traversable $inner
232+
* @param mixed $outerKeySelector (outerValue, outerKey, outer) -> joinKey
233+
* @param mixed $innerKeySelector (innerValue, innerKey, outer) -> joinKey
234+
* @param callable $resultValueSelector (outerValue, innerValue) -> resultValue
235+
* @return Collection
236+
*/
237+
public function outerJoin($inner, $outerKeySelector, $innerKeySelector, callable $resultValueSelector)
238+
{
239+
$outer = $this->getSource();
240+
$outerKeySelector = $this->resolveSelector($outerKeySelector);
241+
$innerKeySelector = $this->resolveSelector($innerKeySelector);
242+
return $this->newCollection($this->getProvider()->outerJoin($outer, $inner, $outerKeySelector, $innerKeySelector, $resultValueSelector));
243+
}
244+
230245
/**
231246
* @param array|\Traversable $inner
232247
* @param mixed $outerKeySelector (outerValue, outerKey, outer) -> joinKey

src/Iterator/JoinIterator.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ public function next()
126126
*/
127127
public function rewind()
128128
{
129-
$this->lookup();
129+
$this->buildLookupTable();
130130
$this->outer->rewind();
131131
$this->fetchInners();
132132
}
@@ -177,7 +177,7 @@ private function fetchInners()
177177
}
178178
}
179179

180-
private function lookup()
180+
private function buildLookupTable()
181181
{
182182
$this->lookupTable = [];
183183
foreach ($this->inner as $innerKey => $innerValue) {

src/Iterator/OuterJoinIterator.php

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
<?php
2+
3+
namespace Emonkak\Collection\Iterator;
4+
5+
class OuterJoinIterator implements \Iterator
6+
{
7+
/**
8+
* The ourter iterator
9+
*
10+
* @var Iterator
11+
*/
12+
private $outer;
13+
14+
/**
15+
* The inner iterator
16+
*
17+
* @var Iterator
18+
*/
19+
private $inner;
20+
21+
/**
22+
* The outer key selector function
23+
*
24+
* @var callable
25+
*/
26+
private $outerKeySelector;
27+
28+
/**
29+
* The inner key selector function
30+
*
31+
* @var callable
32+
*/
33+
private $innerKeySelector;
34+
35+
/**
36+
* The inner result value selector function
37+
*
38+
* @var callable
39+
*/
40+
private $resultValueSelector;
41+
42+
/**
43+
* @var array
44+
*/
45+
private $lookupTable;
46+
47+
/**
48+
* The Key of the outer iterator
49+
*
50+
* @var mixed
51+
*/
52+
private $outerKey;
53+
54+
/**
55+
* The value of the outer iterator
56+
*
57+
* @var mixed
58+
*/
59+
private $outerValue;
60+
61+
/**
62+
* @var mixed
63+
*/
64+
private $resultValue;
65+
66+
/**
67+
* @var array
68+
*/
69+
private $inners = [];
70+
71+
/**
72+
* @param \Iterator $outer
73+
* @param \Iterator $inner
74+
* @param callable $outerKeySelector
75+
* @param callable $innerKeySelector
76+
* @param callable $resultValueSelector
77+
*/
78+
public function __construct(
79+
\Iterator $outer,
80+
\Iterator $inner,
81+
callable $outerKeySelector,
82+
callable $innerKeySelector,
83+
callable $resultValueSelector
84+
) {
85+
$this->outer = $outer;
86+
$this->inner = $inner;
87+
$this->outerKeySelector = $outerKeySelector;
88+
$this->innerKeySelector = $innerKeySelector;
89+
$this->resultValueSelector = $resultValueSelector;
90+
}
91+
92+
/**
93+
* @see \Iterator
94+
* @return mixed
95+
*/
96+
public function current()
97+
{
98+
return $this->resultValue;
99+
}
100+
101+
/**
102+
* @see \Iterator
103+
* @return string
104+
*/
105+
public function key()
106+
{
107+
return $this->outerKey;
108+
}
109+
110+
/**
111+
* @see \Iterator
112+
*/
113+
public function next()
114+
{
115+
next($this->inners);
116+
if (key($this->inners) !== null) {
117+
$this->fetchResultValue();
118+
} else {
119+
$this->outer->next();
120+
$this->fetchInners();
121+
}
122+
}
123+
124+
/**
125+
* @see \Iterator
126+
*/
127+
public function rewind()
128+
{
129+
$this->buildLookupTable();
130+
$this->outer->rewind();
131+
$this->fetchInners();
132+
}
133+
134+
/**
135+
* @see \Iterator
136+
* @return boolean
137+
*/
138+
public function valid()
139+
{
140+
return $this->outer->valid();
141+
}
142+
143+
private function fetchResultValue()
144+
{
145+
if ($this->outer->valid()) {
146+
$this->resultValue = call_user_func(
147+
$this->resultValueSelector,
148+
$this->outerValue,
149+
current($this->inners)
150+
);
151+
}
152+
}
153+
154+
private function fetchInners()
155+
{
156+
if ($this->outer->valid()) {
157+
$this->outerValue = $this->outer->current();
158+
$this->outerKey = $this->outer->key();
159+
160+
$joinKey = call_user_func(
161+
$this->outerKeySelector,
162+
$this->outerValue,
163+
$this->outerKey,
164+
$this->outer
165+
);
166+
if (isset($this->lookupTable[$joinKey])) {
167+
$this->inners = $this->lookupTable[$joinKey];
168+
$this->resultValue = call_user_func(
169+
$this->resultValueSelector,
170+
$this->outerValue,
171+
reset($this->inners)
172+
);
173+
} else {
174+
$this->inners = [];
175+
$this->resultValue = call_user_func(
176+
$this->resultValueSelector,
177+
$this->outerValue,
178+
null
179+
);
180+
}
181+
}
182+
}
183+
184+
private function buildLookupTable()
185+
{
186+
$this->lookupTable = [];
187+
foreach ($this->inner as $innerKey => $innerValue) {
188+
$joinKey = call_user_func(
189+
$this->innerKeySelector,
190+
$innerValue,
191+
$innerKey,
192+
$this->inner
193+
);
194+
$this->lookupTable[$joinKey][] = $innerValue;
195+
}
196+
}
197+
}

src/Provider/ArrayProvider.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,50 @@ public function join($outer, $inner, callable $outerKeySelector, callable $inner
9595
return $results;
9696
}
9797

98+
/**
99+
* {@inheritdoc}
100+
*/
101+
public function outerJoin($outer, $inner, callable $outerKeySelector, callable $innerKeySelector, callable $resultValueSelector)
102+
{
103+
$lookupTable = [];
104+
foreach ($inner as $innerKey => $innerValue) {
105+
$joinKey = call_user_func(
106+
$innerKeySelector,
107+
$innerValue,
108+
$innerKey,
109+
$inner
110+
);
111+
$lookupTable[$joinKey][] = $innerValue;
112+
}
113+
114+
$results = [];
115+
foreach ($outer as $outerKey => $outerValue) {
116+
$joinKey = call_user_func(
117+
$outerKeySelector,
118+
$outerValue,
119+
$outerKey,
120+
$outer
121+
);
122+
if (isset($lookupTable[$joinKey])) {
123+
foreach ($lookupTable[$joinKey] as $innerValue) {
124+
$results[] = call_user_func(
125+
$resultValueSelector,
126+
$outerValue,
127+
$innerValue
128+
);
129+
}
130+
} else {
131+
$results[] = call_user_func(
132+
$resultValueSelector,
133+
$outerValue,
134+
null
135+
);
136+
}
137+
}
138+
139+
return $results;
140+
}
141+
98142
/**
99143
* {@inheritdoc}
100144
*/

src/Provider/GeneratorProvider.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,55 @@ public function join($outer, $inner, callable $outerKeySelector, callable $inner
100100
});
101101
}
102102

103+
/**
104+
* {@inheritdoc}
105+
*/
106+
public function outerJoin($outer, $inner, callable $outerKeySelector, callable $innerKeySelector, callable $resultValueSelector)
107+
{
108+
return Iterators::createLazy(function() use (
109+
$outer,
110+
$inner,
111+
$outerKeySelector,
112+
$innerKeySelector,
113+
$resultValueSelector
114+
) {
115+
$lookupTable = [];
116+
foreach ($inner as $innerKey => $innerValue) {
117+
$joinKey = call_user_func(
118+
$innerKeySelector,
119+
$innerValue,
120+
$innerKey,
121+
$inner
122+
);
123+
$lookupTable[$joinKey][] = $innerValue;
124+
}
125+
126+
foreach ($outer as $outerKey => $outerValue) {
127+
$joinKey = call_user_func(
128+
$outerKeySelector,
129+
$outerValue,
130+
$outerKey,
131+
$outer
132+
);
133+
if (isset($lookupTable[$joinKey])) {
134+
foreach ($lookupTable[$joinKey] as $innerValue) {
135+
yield call_user_func(
136+
$resultValueSelector,
137+
$outerValue,
138+
$innerValue
139+
);
140+
}
141+
} else {
142+
yield call_user_func(
143+
$resultValueSelector,
144+
$outerValue,
145+
null
146+
);
147+
}
148+
}
149+
});
150+
}
151+
103152
/**
104153
* {@inheritdoc}
105154
*/

src/Provider/ICollectionProvider.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ public function filter($xs, callable $predicate);
3636
*/
3737
public function join($outer, $inner, callable $outerKeySelector, callable $innerKeySelector, callable $resultValueSelector);
3838

39+
/**
40+
* @param array|\Traversable $outer
41+
* @param array|\Traversable $inner
42+
* @param mixed $outerKeySelector (outerValue, outerKey, outer) -> joinKey
43+
* @param mixed $innerKeySelector (innerValue, innerKey, outer) -> joinKey
44+
* @param callable $resultValueSelector (outerValue, innerValue) -> resultValue
45+
* @return array|\Iterator
46+
*/
47+
public function outerJoin($outer, $inner, callable $outerKeySelector, callable $innerKeySelector, callable $resultValueSelector);
48+
3949
/**
4050
* @param array|\Traversable $outer
4151
* @param array|\Traversable $inner

src/Provider/IteratorProvider.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Emonkak\Collection\Iterator\JoinIterator;
1414
use Emonkak\Collection\Iterator\MapIterator;
1515
use Emonkak\Collection\Iterator\MemoizeIterator;
16+
use Emonkak\Collection\Iterator\OuterJoinIterator;
1617
use Emonkak\Collection\Iterator\RangeIterator;
1718
use Emonkak\Collection\Iterator\RenumIterator;
1819
use Emonkak\Collection\Iterator\RepeatIterator;
@@ -70,6 +71,20 @@ public function join($outer, $inner, callable $outerKeySelector, callable $inner
7071
);
7172
}
7273

74+
/**
75+
* {@inheritdoc}
76+
*/
77+
public function outerJoin($outer, $inner, callable $outerKeySelector, callable $innerKeySelector, callable $resultValueSelector)
78+
{
79+
return new OuterJoinIterator(
80+
Iterators::create($outer),
81+
Iterators::create($inner),
82+
$outerKeySelector,
83+
$innerKeySelector,
84+
$resultValueSelector
85+
);
86+
}
87+
7388
/**
7489
* {@inheritdoc}
7590
*/

0 commit comments

Comments
 (0)