Skip to content

Commit 47f0b5a

Browse files
committed
added Iterables::memoize()
1 parent 6310f42 commit 47f0b5a

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
@@ -158,6 +158,49 @@ public static function map(iterable $iterable, callable $transformer): \Generato
158158
}
159159

160160

161+
/**
162+
* Wraps around iterator and caches its keys and values during iteration.
163+
* This allows the data to be re-iterated multiple times.
164+
* @template K
165+
* @template V
166+
* @param iterable<K, V> $iterable
167+
* @return \IteratorAggregate<K, V>
168+
*/
169+
public static function memoize(iterable $iterable): iterable
170+
{
171+
return new class (self::toIterator($iterable)) implements \IteratorAggregate {
172+
public function __construct(
173+
private iterable $iterable,
174+
private array $cache = [],
175+
) {
176+
}
177+
178+
179+
public function getIterator(): \Generator
180+
{
181+
if (!$this->cache) {
182+
$this->iterable->rewind();
183+
}
184+
$i = 0;
185+
while (true) {
186+
if (isset($this->cache[$i])) {
187+
[$k, $v] = $this->cache[$i];
188+
} elseif ($this->iterable->valid()) {
189+
$k = $this->iterable->key();
190+
$v = $this->iterable->current();
191+
$this->iterable->next();
192+
$this->cache[$i] = [$k, $v];
193+
} else {
194+
break;
195+
}
196+
yield $k => $v;
197+
$i++;
198+
}
199+
}
200+
};
201+
}
202+
203+
161204
/**
162205
* Creates an iterator from anything that is iterable.
163206
* @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)