Skip to content

Commit 1534be2

Browse files
lippserdnilmerg
authored andcommitted
Introduce yield_groups()
1 parent d542e1c commit 1534be2

2 files changed

Lines changed: 190 additions & 1 deletion

File tree

src/functions.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
namespace ipl\Stdlib;
44

5+
use Generator;
56
use InvalidArgumentException;
7+
use IteratorIterator;
68
use Traversable;
79
use stdClass;
810

@@ -69,3 +71,42 @@ function iterable_key_first($iterable)
6971

7072
return null;
7173
}
74+
75+
/**
76+
* Yield sets of items from a sorted traversable grouped by a specific criterion gathered from a callback
77+
*
78+
* The traversable must be sorted by the criterion. The callback must return at least the criterion,
79+
* but can also return value and key in addition.
80+
*
81+
* @param Traversable<mixed, mixed> $traversable
82+
* @param callable(mixed $value, mixed $key): array{0: mixed, 1?: mixed, 2?: mixed} $groupBy
83+
*
84+
* @return Generator
85+
*/
86+
function yield_groups(Traversable $traversable, callable $groupBy): Generator
87+
{
88+
$iterator = new IteratorIterator($traversable);
89+
$iterator->rewind();
90+
91+
if (! $iterator->valid()) {
92+
return;
93+
}
94+
95+
list($criterion, $v, $k) = array_pad((array) $groupBy($iterator->current(), $iterator->key()), 3, null);
96+
$group = [$k ?? $iterator->key() => $v ?? $iterator->current()];
97+
98+
$iterator->next();
99+
for (; $iterator->valid(); $iterator->next()) {
100+
list($c, $v, $k) = array_pad((array) $groupBy($iterator->current(), $iterator->key()), 3, null);
101+
if ($c !== $criterion) {
102+
yield $criterion => $group;
103+
104+
$group = [];
105+
$criterion = $c;
106+
}
107+
108+
$group[$k ?? $iterator->key()] = $v ?? $iterator->current();
109+
}
110+
111+
yield $criterion => $group;
112+
}

tests/FunctionsTest.php

Lines changed: 149 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,44 @@
33
namespace ipl\Tests\Stdlib;
44

55
use ArrayIterator;
6+
use EmptyIterator;
67
use InvalidArgumentException;
7-
use stdClass;
88
use ipl\Stdlib;
9+
use stdClass;
910

1011
class FunctionsTest extends TestCase
1112
{
13+
protected const YIELD_GROUPS_DATA = [
14+
'one' => [
15+
'group' => 1,
16+
'id' => 1
17+
],
18+
'two' => [
19+
'group' => 1,
20+
'id' => 2
21+
],
22+
'three' => [
23+
'group' => 1,
24+
'id' => 3
25+
],
26+
'four' => [
27+
'group' => 2,
28+
'id' => 4
29+
],
30+
'five' => [
31+
'group' => 3,
32+
'id' => 5
33+
],
34+
'six' => [
35+
'group' => 3,
36+
'id' => 6
37+
],
38+
'seven' => [
39+
'group' => 3,
40+
'id' => 7
41+
]
42+
];
43+
1244
public function testGetPhpTypeWithObject()
1345
{
1446
$object = (object) [];
@@ -98,4 +130,120 @@ public function testIterableKeyFirstReturnsNullIfIterableIsGeneratorAndIsEmpty()
98130
yield;
99131
})));
100132
}
133+
134+
public function testYieldGroupsWithEmptyIterator()
135+
{
136+
$this->assertEquals([], iterator_to_array(Stdlib\yield_groups(new EmptyIterator(), function () {
137+
})));
138+
}
139+
140+
public function testYieldGroupsCallbackArguments()
141+
{
142+
iterator_to_array(
143+
Stdlib\yield_groups(new ArrayIterator(static::YIELD_GROUPS_DATA), function (array $v, string $k): int {
144+
$this->assertSame(static::YIELD_GROUPS_DATA[$k], $v);
145+
146+
return $v['group'];
147+
})
148+
);
149+
}
150+
151+
public function testYieldGroupsWithCallbackReturningCriterion()
152+
{
153+
$this->assertEquals(
154+
[
155+
1 => [
156+
'one' => [
157+
'group' => 1,
158+
'id' => 1
159+
],
160+
'two' => [
161+
'group' => 1,
162+
'id' => 2
163+
],
164+
'three' => [
165+
'group' => 1,
166+
'id' => 3
167+
]
168+
],
169+
2 => [
170+
'four' => [
171+
'group' => 2,
172+
'id' => 4
173+
]
174+
],
175+
3 => [
176+
'five' => [
177+
'group' => 3,
178+
'id' => 5
179+
],
180+
'six' => [
181+
'group' => 3,
182+
'id' => 6
183+
],
184+
'seven' => [
185+
'group' => 3,
186+
'id' => 7
187+
]
188+
]
189+
],
190+
iterator_to_array(
191+
Stdlib\yield_groups(new ArrayIterator(static::YIELD_GROUPS_DATA), function (array $v): int {
192+
return $v['group'];
193+
})
194+
)
195+
);
196+
}
197+
198+
public function testYieldGroupsWithCallbackReturningCriterionAndValue()
199+
{
200+
$this->assertEquals(
201+
[
202+
1 => [
203+
'one' => 1,
204+
'two' => 2,
205+
'three' => 3
206+
],
207+
2 => [
208+
'four' => 4,
209+
],
210+
3 => [
211+
'five' => 5,
212+
'six' => 6,
213+
'seven' => 7
214+
]
215+
],
216+
iterator_to_array(
217+
Stdlib\yield_groups(new ArrayIterator(static::YIELD_GROUPS_DATA), function (array $v): array {
218+
return [$v['group'], $v['id']];
219+
})
220+
)
221+
);
222+
}
223+
224+
public function testYieldGroupsWithCallbackReturningCriterionValueAndKey()
225+
{
226+
$this->assertEquals(
227+
[
228+
1 => [
229+
1 => 1,
230+
2 => 2,
231+
3 => 3
232+
],
233+
2 => [
234+
4 => 4
235+
],
236+
3 => [
237+
5 => 5,
238+
6 => 6,
239+
7 => 7
240+
]
241+
],
242+
iterator_to_array(
243+
Stdlib\yield_groups(new ArrayIterator(static::YIELD_GROUPS_DATA), function (array $v): array {
244+
return [$v['group'], $v['id'], $v['id']];
245+
})
246+
)
247+
);
248+
}
101249
}

0 commit comments

Comments
 (0)