From 74f7aa2abd769d03b416385c2a46a0df0d065e23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kry=C5=A1tof=20Ko=C5=99=C3=ADnek?= Date: Thu, 26 Sep 2024 17:30:52 +0200 Subject: [PATCH] Add `RecursiveIterableAggregate` (#61) * Add `RecursiveIterableAggregate` * Change `RecursiveIterableAggregate` recursive call to loop * Update `README` to include `RecursiveIterableAggregate` --- README.md | 44 +++++++ src/RecursiveIterableAggregate.php | 62 ++++++++++ tests/unit/RecursiveIterableAggregateTest.php | 107 ++++++++++++++++++ 3 files changed, 213 insertions(+) create mode 100644 src/RecursiveIterableAggregate.php create mode 100644 tests/unit/RecursiveIterableAggregateTest.php diff --git a/README.md b/README.md index 9f84906..1c302f4 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ The missing PHP iterators. - `PackIterableAggregate` - `PausableIteratorAggregate` - `RandomIterableAggregate` +- `RecursiveIterableAggregate` - `ReductionIterableAggregate` - `ResourceIteratorAggregate` - `SimpleCachingIteratorAggregate` @@ -308,6 +309,49 @@ foreach ($iterator as $v) { } ``` +### RecursiveIterableAggregate + +This iterator allows you to iterate through tree-like structures by simply +providing an `iterable` and callback to access its children. + +```php + '1', + 'children' => [ + [ + 'value' => '1.1', + 'children' => [ + [ + 'value' => '1.1.1', + 'children' => [], + ], + ], + ], + [ + 'value' => '1.2', + 'children' => [], + ], + ], + ], + [ + 'value' => '2', + 'children' => [], + ], +]; + +$iterator = new RecursiveIterableAggregate( + $treeStructure, + fn (array $i) => $i['children'] +); + +foreach ($iterator as $item) { + var_dump($item['value']); // This will print '1', '1.1', '1.1.1', '1.2', '2' +} +``` + ### ReductionIterableAggregate ```php diff --git a/src/RecursiveIterableAggregate.php b/src/RecursiveIterableAggregate.php new file mode 100644 index 0000000..3bf928d --- /dev/null +++ b/src/RecursiveIterableAggregate.php @@ -0,0 +1,62 @@ + + */ +class RecursiveIterableAggregate implements IteratorAggregate +{ + /** + * @param iterable $iterable + * @param (Closure(T, TKey, iterable): iterable) $closure + */ + public function __construct(private iterable $iterable, private Closure $closure) {} + + /** + * @return Generator + */ + public function getIterator(): Generator + { + /** @var SplStack $iterables */ + $iterables = new SplStack(); + $iterables->push(new IterableIterator($this->iterable)); + + while (!$iterables->isEmpty()) { + $currentIterable = $iterables->top(); + + while ($currentIterable->valid()) { + $currentValue = $currentIterable->current(); + $currentKey = $currentIterable->key(); + + yield $currentKey => $currentValue; + + $subIterable = new IterableIterator( + ($this->closure)($currentValue, $currentKey, $this->iterable) + ); + + $currentIterable->next(); + $subIterable->rewind(); + + if ($subIterable->valid()) { + $iterables->push($subIterable); + + continue 2; + } + } + + $iterables->pop(); + } + } +} diff --git a/tests/unit/RecursiveIterableAggregateTest.php b/tests/unit/RecursiveIterableAggregateTest.php new file mode 100644 index 0000000..51f73aa --- /dev/null +++ b/tests/unit/RecursiveIterableAggregateTest.php @@ -0,0 +1,107 @@ + + */ + public static function provideBasicCases(): iterable + { + yield [ + [ + 'i0' => [ + 'index' => 0, + 'children' => [], + ], + 'i1' => [ + 'index' => 1, + 'children' => [], + ], + ], + [ + 'i0' => [ + 'index' => 0, + 'children' => [], + ], + 'i1' => [ + 'index' => 1, + 'children' => [], + ], + ], + ]; + + yield [ + [ + 'i0' => [ + 'index' => 0, + 'children' => [ + 'i1' => [ + 'index' => 1, + 'children' => [ + 'i2' => [ + 'index' => 2, + 'children' => [], + ], + ], + ], + ], + ], + ], + [ + 'i0' => [ + 'index' => 0, + 'children' => [ + 'i1' => [ + 'index' => 1, + 'children' => [ + 'i2' => [ + 'index' => 2, + 'children' => [], + ], + ], + ], + ], + ], + 'i1' => [ + 'index' => 1, + 'children' => [ + 'i2' => [ + 'index' => 2, + 'children' => [], + ], + ], + ], + 'i2' => [ + 'index' => 2, + 'children' => [], + ], + ], + ]; + } + + /** + * @dataProvider provideBasicCases + */ + public function testBasic(array $input, array $expected): void + { + self::assertEquals( + iterator_to_array( + new RecursiveIterableAggregate($input, static fn (array $i) => $i['children']) + ), + $expected + ); + } +}