Last active
May 3, 2021 14:32
-
-
Save gskema/0ef5fff402b3ecbe2ed0334250590f7d to your computer and use it in GitHub Desktop.
PHP Nested Iterator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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