diff --git a/src/Utils/Iterables.php b/src/Utils/Iterables.php index e64f8f13..e9651416 100644 --- a/src/Utils/Iterables.php +++ b/src/Utils/Iterables.php @@ -158,6 +158,49 @@ public static function map(iterable $iterable, callable $transformer): \Generato } + /** + * Wraps around iterator and caches its keys and values during iteration. + * This allows the data to be re-iterated multiple times. + * @template K + * @template V + * @param iterable $iterable + * @return \IteratorAggregate + */ + public static function memoize(iterable $iterable): iterable + { + return new class (self::toIterator($iterable)) implements \IteratorAggregate { + public function __construct( + private iterable $iterable, + private array $cache = [], + ) { + } + + + public function getIterator(): \Generator + { + if (!$this->cache) { + $this->iterable->rewind(); + } + $i = 0; + while (true) { + if (isset($this->cache[$i])) { + [$k, $v] = $this->cache[$i]; + } elseif ($this->iterable->valid()) { + $k = $this->iterable->key(); + $v = $this->iterable->current(); + $this->iterable->next(); + $this->cache[$i] = [$k, $v]; + } else { + break; + } + yield $k => $v; + $i++; + } + } + }; + } + + /** * Creates an iterator from anything that is iterable. * @template K diff --git a/tests/Utils/Iterables.memoize().phpt b/tests/Utils/Iterables.memoize().phpt new file mode 100644 index 00000000..47b2c94e --- /dev/null +++ b/tests/Utils/Iterables.memoize().phpt @@ -0,0 +1,77 @@ + 'apple'; + yield ['b'] => ['banana']; + yield 'c' => 'cherry'; +} + + +test('iteration', function () { + $iterator = Iterables::memoize(iterator()); + + $pairs = []; + foreach ($iterator as $key => $value) { + $pairs[] = [$key, $value]; + } + Assert::same( + [ + ['a', 'apple'], + [['b'], ['banana']], + ['c', 'cherry'], + ], + $pairs, + ); +}); + + +test('re-iteration', function () { + $iterator = Iterables::memoize(iterator()); + + foreach ($iterator as $value); + + $pairs = []; + foreach ($iterator as $key => $value) { + $pairs[] = [$key, $value]; + } + Assert::same( + [ + ['a', 'apple'], + [['b'], ['banana']], + ['c', 'cherry'], + ], + $pairs, + ); +}); + + +test('nested re-iteration', function () { + $iterator = Iterables::memoize(iterator()); + + $pairs = []; + foreach ($iterator as $key => $value) { + $pairs[] = [$key, $value]; + foreach ($iterator as $value); + } + Assert::same( + [ + ['a', 'apple'], + [['b'], ['banana']], + ['c', 'cherry'], + ], + $pairs, + ); +});