Skip to content

Commit c7ae954

Browse files
committed
added Iterables::memoize()
1 parent b7631f0 commit c7ae954

File tree

2 files changed

+120
-0
lines changed

2 files changed

+120
-0
lines changed

src/Utils/Iterables.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,49 @@ public static function map(iterable $iterable, callable $transformer): \Generato
156156
}
157157

158158

159+
/**
160+
* Wraps around iterator and caches its keys and values during iteration.
161+
* This allows the data to be re-iterated multiple times.
162+
* @template K
163+
* @template V
164+
* @param iterable<K, V> $iterable
165+
* @return \IteratorAggregate<K, V>
166+
*/
167+
public static function memoize(iterable $iterable): iterable
168+
{
169+
return new class (self::toIterator($iterable)) implements \IteratorAggregate {
170+
public function __construct(
171+
private \Iterator $iterator,
172+
private array $cache = [],
173+
) {
174+
}
175+
176+
177+
public function getIterator(): \Generator
178+
{
179+
if (!$this->cache) {
180+
$this->iterator->rewind();
181+
}
182+
$i = 0;
183+
while (true) {
184+
if (isset($this->cache[$i])) {
185+
[$k, $v] = $this->cache[$i];
186+
} elseif ($this->iterator->valid()) {
187+
$k = $this->iterator->key();
188+
$v = $this->iterator->current();
189+
$this->iterator->next();
190+
$this->cache[$i] = [$k, $v];
191+
} else {
192+
break;
193+
}
194+
yield $k => $v;
195+
$i++;
196+
}
197+
}
198+
};
199+
}
200+
201+
159202
/**
160203
* Creates an iterator from anything that is iterable.
161204
* @template K
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
/**
4+
* Test: Nette\Utils\Iterables::memoize()
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
use Nette\Utils\Iterables;
10+
use Tester\Assert;
11+
12+
require __DIR__ . '/../bootstrap.php';
13+
14+
15+
function iterator(): Generator
16+
{
17+
yield 'a' => 'apple';
18+
yield ['b'] => ['banana'];
19+
yield 'c' => 'cherry';
20+
}
21+
22+
23+
test('iteration', function () {
24+
$iterator = Iterables::memoize(iterator());
25+
26+
$pairs = [];
27+
foreach ($iterator as $key => $value) {
28+
$pairs[] = [$key, $value];
29+
}
30+
Assert::same(
31+
[
32+
['a', 'apple'],
33+
[['b'], ['banana']],
34+
['c', 'cherry'],
35+
],
36+
$pairs,
37+
);
38+
});
39+
40+
41+
test('re-iteration', function () {
42+
$iterator = Iterables::memoize(iterator());
43+
44+
foreach ($iterator as $value);
45+
46+
$pairs = [];
47+
foreach ($iterator as $key => $value) {
48+
$pairs[] = [$key, $value];
49+
}
50+
Assert::same(
51+
[
52+
['a', 'apple'],
53+
[['b'], ['banana']],
54+
['c', 'cherry'],
55+
],
56+
$pairs,
57+
);
58+
});
59+
60+
61+
test('nested re-iteration', function () {
62+
$iterator = Iterables::memoize(iterator());
63+
64+
$pairs = [];
65+
foreach ($iterator as $key => $value) {
66+
$pairs[] = [$key, $value];
67+
foreach ($iterator as $value);
68+
}
69+
Assert::same(
70+
[
71+
['a', 'apple'],
72+
[['b'], ['banana']],
73+
['c', 'cherry'],
74+
],
75+
$pairs,
76+
);
77+
});

0 commit comments

Comments
 (0)