Skip to content

Commit

Permalink
Add RecursiveIterableAggregate (#61)
Browse files Browse the repository at this point in the history
* Add `RecursiveIterableAggregate`

* Change `RecursiveIterableAggregate` recursive call to loop

* Update `README` to include `RecursiveIterableAggregate`
  • Loading branch information
bobkorinek authored Sep 26, 2024
1 parent a443a53 commit 74f7aa2
Show file tree
Hide file tree
Showing 3 changed files with 213 additions and 0 deletions.
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ The missing PHP iterators.
- `PackIterableAggregate`
- `PausableIteratorAggregate`
- `RandomIterableAggregate`
- `RecursiveIterableAggregate`
- `ReductionIterableAggregate`
- `ResourceIteratorAggregate`
- `SimpleCachingIteratorAggregate`
Expand Down Expand Up @@ -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
<?php

$treeStructure = [
[
'value' => '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
Expand Down
62 changes: 62 additions & 0 deletions src/RecursiveIterableAggregate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

declare(strict_types=1);

namespace loophp\iterators;

use Closure;
use Generator;
use Iterator;
use IteratorAggregate;
use SplStack;

/**
* @template TKey
* @template T
*
* @implements IteratorAggregate<TKey, T>
*/
class RecursiveIterableAggregate implements IteratorAggregate
{
/**
* @param iterable<TKey, T> $iterable
* @param (Closure(T, TKey, iterable<TKey, T>): iterable<TKey, T>) $closure
*/
public function __construct(private iterable $iterable, private Closure $closure) {}

/**
* @return Generator<TKey, T>
*/
public function getIterator(): Generator
{
/** @var SplStack<Iterator> $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();
}
}
}
107 changes: 107 additions & 0 deletions tests/unit/RecursiveIterableAggregateTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?php

declare(strict_types=1);

namespace tests\loophp\iterators;

use Generator;
use loophp\iterators\RecursiveIterableAggregate;
use PHPUnit\Framework\TestCase;

/**
* @internal
*
* @coversDefaultClass \loophp\iterators
*/
final class RecursiveIterableAggregateTest extends TestCase
{
/**
* @return Generator<array{0: array, 1: array}>
*/
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
);
}
}

0 comments on commit 74f7aa2

Please sign in to comment.