Skip to content

Instantly share code, notes, and snippets.

@gskema
Last active May 3, 2021 14:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gskema/0ef5fff402b3ecbe2ed0334250590f7d to your computer and use it in GitHub Desktop.
Save gskema/0ef5fff402b3ecbe2ed0334250590f7d to your computer and use it in GitHub Desktop.
PHP Nested Iterator
<?php
namespace Iterator;
use ArrayIterator;
use Iterator;
/**
* Takes a set of iterators [iterator1, iterator2, iterator3, ...]
* and iterates in an equivalent way to:
*
* foreach(iterator1) {
* foreach(iterator2) {
* foreach(iterator3) {
* ...(arg1, arg2, arg3)
*
* @see NestedIteratorTest
*/
class NestedIterator implements Iterator
{
/**
* Must have ArrayAccess + Iterator interfaces.
*
* @var mixed[][]|ArrayIterator[]
*/
protected $iterators = [];
/** @var int[]|string[] */
protected $keys = [];
/**
* @param mixed[][]|ArrayIterator[] $iterators
*/
public function __construct(array $iterators)
{
$this->iterators = $iterators;
$this->keys = array_keys($iterators);
}
/**
* @inheritDoc
* @return mixed[]
*/
public function current(): array
{
$values = [];
foreach ($this->keys as $key) {
$value = $this->iterators[$key] instanceof ArrayIterator
? $this->iterators[$key]->current()
: current($this->iterators[$key]);
$values[] = $value;
}
return $values;
}
/**
* @inheritDoc
*/
public function next(): void
{
$firstKey = $this->keys[0] ?? null;
foreach (array_reverse($this->keys) as $key) {
$this->iterators[$key] instanceof ArrayIterator
? $this->iterators[$key]->next()
: next($this->iterators[$key]);
$innerKey = $this->iterators[$key] instanceof ArrayIterator
? $this->iterators[$key]->key()
: key($this->iterators[$key]);
if (null === $innerKey) {
if ($firstKey === $key) {
break;
} else {
$this->iterators[$key] instanceof ArrayIterator
? $this->iterators[$key]->rewind()
: reset($this->iterators[$key]);
}
} else {
break;
}
}
}
/**
* @inheritDoc
*/
public function key(): ?string
{
$innerKeys = [];
foreach ($this->keys as $key) {
$innerKey = $this->iterators[$key] instanceof ArrayIterator
? $this->iterators[$key]->key()
: key($this->iterators[$key]);
if (null === $innerKey) {
return null;
}
$innerKeys[] = $innerKey;
}
return implode('_', $innerKeys);
}
/**
* @inheritDoc
*/
public function valid(): bool
{
foreach ($this->keys as $key) {
$innerKey = $this->iterators[$key] instanceof ArrayIterator
? $this->iterators[$key]->key()
: key($this->iterators[$key]);
if (null === $innerKey) {
return false;
}
}
return true;
}
/**
* @inheritDoc
*/
public function rewind(): void
{
foreach ($this->keys as $key) {
$this->iterators[$key] instanceof ArrayIterator
? $this->iterators[$key]->rewind()
: reset($this->iterators[$key]);
}
}
}
class NestedIteratorTest extends TestCase
{
/**
* @return mixed[][]
*/
public function dataIterate(): array
{
$dataSets = [];
// #0
$dataSets[] = [
new NestedIterator([
['a', 'b', 'c', 'd'],
[1, 2, 3, 4]
]),
[
['a', 1],
['a', 2],
['a', 3],
['a', 4],
['b', 1],
['b', 2],
['b', 3],
['b', 4],
['c', 1],
['c', 2],
['c', 3],
['c', 4],
['d', 1],
['d', 2],
['d', 3],
['d', 4],
],
];
// #1
$dataSets[] = [
new NestedIterator([
['a', 'b'],
[1, 2],
['@', '$']
]),
[
['a', 1, '@'],
['a', 1, '$'],
['a', 2, '@'],
['a', 2, '$'],
['b', 1, '@'],
['b', 1, '$'],
['b', 2, '@'],
['b', 2, '$'],
],
];
// #2
$dataSets[] = [
new NestedIterator([
['a', 'b'],
[1, 2],
[]
]),
[],
];
// #3
$dataSets[] = [
new NestedIterator([
'a' => ['a', 'b'],
'b' => [1, 2],
'c' => ['@', '$']
]),
[
['a', 1, '@'],
['a', 1, '$'],
['a', 2, '@'],
['a', 2, '$'],
['b', 1, '@'],
['b', 1, '$'],
['b', 2, '@'],
['b', 2, '$'],
],
];
// #4
$dataSets[] = [
new NestedIterator([
'a' => new ArrayIterator(['a', 'b']),
'b' => new ArrayIterator([1, 2]),
'c' => new ArrayIterator(['@', '$']),
]),
[
['a', 1, '@'],
['a', 1, '$'],
['a', 2, '@'],
['a', 2, '$'],
['b', 1, '@'],
['b', 1, '$'],
['b', 2, '@'],
['b', 2, '$'],
],
];
return $dataSets;
}
/**
* @dataProvider dataIterate
*
* @param NestedIterator $givenIterator
* @param mixed[] $expectedValues
*/
public function testIterate(
NestedIterator $givenIterator,
array $expectedValues
): void {
for ($i = 1; $i <= 2; $i++) {
$actualValues = [];
foreach ($givenIterator as $key => $value) {
// echo $key.' -> '.json_encode($value).PHP_EOL;
$actualValues[] = $value;
}
self::assertEquals($expectedValues, $actualValues);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment