Skip to content

Commit 74f7aa2

Browse files
authored
Add RecursiveIterableAggregate (#61)
* Add `RecursiveIterableAggregate` * Change `RecursiveIterableAggregate` recursive call to loop * Update `README` to include `RecursiveIterableAggregate`
1 parent a443a53 commit 74f7aa2

File tree

3 files changed

+213
-0
lines changed

3 files changed

+213
-0
lines changed

README.md

+44
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ The missing PHP iterators.
3030
- `PackIterableAggregate`
3131
- `PausableIteratorAggregate`
3232
- `RandomIterableAggregate`
33+
- `RecursiveIterableAggregate`
3334
- `ReductionIterableAggregate`
3435
- `ResourceIteratorAggregate`
3536
- `SimpleCachingIteratorAggregate`
@@ -308,6 +309,49 @@ foreach ($iterator as $v) {
308309
}
309310
```
310311

312+
### RecursiveIterableAggregate
313+
314+
This iterator allows you to iterate through tree-like structures by simply
315+
providing an `iterable` and callback to access its children.
316+
317+
```php
318+
<?php
319+
320+
$treeStructure = [
321+
[
322+
'value' => '1',
323+
'children' => [
324+
[
325+
'value' => '1.1',
326+
'children' => [
327+
[
328+
'value' => '1.1.1',
329+
'children' => [],
330+
],
331+
],
332+
],
333+
[
334+
'value' => '1.2',
335+
'children' => [],
336+
],
337+
],
338+
],
339+
[
340+
'value' => '2',
341+
'children' => [],
342+
],
343+
];
344+
345+
$iterator = new RecursiveIterableAggregate(
346+
$treeStructure,
347+
fn (array $i) => $i['children']
348+
);
349+
350+
foreach ($iterator as $item) {
351+
var_dump($item['value']); // This will print '1', '1.1', '1.1.1', '1.2', '2'
352+
}
353+
```
354+
311355
### ReductionIterableAggregate
312356

313357
```php

src/RecursiveIterableAggregate.php

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace loophp\iterators;
6+
7+
use Closure;
8+
use Generator;
9+
use Iterator;
10+
use IteratorAggregate;
11+
use SplStack;
12+
13+
/**
14+
* @template TKey
15+
* @template T
16+
*
17+
* @implements IteratorAggregate<TKey, T>
18+
*/
19+
class RecursiveIterableAggregate implements IteratorAggregate
20+
{
21+
/**
22+
* @param iterable<TKey, T> $iterable
23+
* @param (Closure(T, TKey, iterable<TKey, T>): iterable<TKey, T>) $closure
24+
*/
25+
public function __construct(private iterable $iterable, private Closure $closure) {}
26+
27+
/**
28+
* @return Generator<TKey, T>
29+
*/
30+
public function getIterator(): Generator
31+
{
32+
/** @var SplStack<Iterator> $iterables */
33+
$iterables = new SplStack();
34+
$iterables->push(new IterableIterator($this->iterable));
35+
36+
while (!$iterables->isEmpty()) {
37+
$currentIterable = $iterables->top();
38+
39+
while ($currentIterable->valid()) {
40+
$currentValue = $currentIterable->current();
41+
$currentKey = $currentIterable->key();
42+
43+
yield $currentKey => $currentValue;
44+
45+
$subIterable = new IterableIterator(
46+
($this->closure)($currentValue, $currentKey, $this->iterable)
47+
);
48+
49+
$currentIterable->next();
50+
$subIterable->rewind();
51+
52+
if ($subIterable->valid()) {
53+
$iterables->push($subIterable);
54+
55+
continue 2;
56+
}
57+
}
58+
59+
$iterables->pop();
60+
}
61+
}
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace tests\loophp\iterators;
6+
7+
use Generator;
8+
use loophp\iterators\RecursiveIterableAggregate;
9+
use PHPUnit\Framework\TestCase;
10+
11+
/**
12+
* @internal
13+
*
14+
* @coversDefaultClass \loophp\iterators
15+
*/
16+
final class RecursiveIterableAggregateTest extends TestCase
17+
{
18+
/**
19+
* @return Generator<array{0: array, 1: array}>
20+
*/
21+
public static function provideBasicCases(): iterable
22+
{
23+
yield [
24+
[
25+
'i0' => [
26+
'index' => 0,
27+
'children' => [],
28+
],
29+
'i1' => [
30+
'index' => 1,
31+
'children' => [],
32+
],
33+
],
34+
[
35+
'i0' => [
36+
'index' => 0,
37+
'children' => [],
38+
],
39+
'i1' => [
40+
'index' => 1,
41+
'children' => [],
42+
],
43+
],
44+
];
45+
46+
yield [
47+
[
48+
'i0' => [
49+
'index' => 0,
50+
'children' => [
51+
'i1' => [
52+
'index' => 1,
53+
'children' => [
54+
'i2' => [
55+
'index' => 2,
56+
'children' => [],
57+
],
58+
],
59+
],
60+
],
61+
],
62+
],
63+
[
64+
'i0' => [
65+
'index' => 0,
66+
'children' => [
67+
'i1' => [
68+
'index' => 1,
69+
'children' => [
70+
'i2' => [
71+
'index' => 2,
72+
'children' => [],
73+
],
74+
],
75+
],
76+
],
77+
],
78+
'i1' => [
79+
'index' => 1,
80+
'children' => [
81+
'i2' => [
82+
'index' => 2,
83+
'children' => [],
84+
],
85+
],
86+
],
87+
'i2' => [
88+
'index' => 2,
89+
'children' => [],
90+
],
91+
],
92+
];
93+
}
94+
95+
/**
96+
* @dataProvider provideBasicCases
97+
*/
98+
public function testBasic(array $input, array $expected): void
99+
{
100+
self::assertEquals(
101+
iterator_to_array(
102+
new RecursiveIterableAggregate($input, static fn (array $i) => $i['children'])
103+
),
104+
$expected
105+
);
106+
}
107+
}

0 commit comments

Comments
 (0)