Skip to content

Commit a9a9f72

Browse files
committed
chore(iter): optimize iterator
Signed-off-by: azjezz <azjezz@protonmail.com>
1 parent 85c91dc commit a9a9f72

File tree

2 files changed

+81
-73
lines changed

2 files changed

+81
-73
lines changed

src/Psl/Iter/Iterator.php

+48-73
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ final class Iterator implements Countable, SeekableIterator
2828
private array $entries = [];
2929

3030
/**
31-
* Whether or not the current value/key pair has been added to the local entries.
31+
* Whether or not the current value/key pair has been added to the local entries.
3232
*/
33-
private bool $saved = false;
33+
private bool $saved = true;
3434

3535
/**
3636
* Current cursor position for the local entries.
@@ -79,7 +79,7 @@ public static function create(iterable $iterable): Iterator
7979
/**
8080
* @var (callable(): Generator<Tsk, Tsv, mixed, void>) $factory
8181
*/
82-
$factory = static fn (): Generator => yield from $iterable;
82+
$factory = static fn(): Generator => yield from $iterable;
8383

8484
return new self($factory());
8585
}
@@ -94,34 +94,34 @@ public static function create(iterable $iterable): Iterator
9494
public function current(): mixed
9595
{
9696
Psl\invariant($this->valid(), 'The Iterator is invalid.');
97-
if (!contains_key($this->entries, $this->position)) {
98-
$this->progress();
99-
}
97+
$this->save();
10098

10199
return $this->entries[$this->position][1];
102100
}
103101

104102
/**
105-
* Move forward to the next element.
103+
* Checks if current position is valid.
106104
*/
107-
public function next(): void
105+
public function valid(): bool
108106
{
109-
$this->position++;
110-
if (null === $this->generator || !$this->generator->valid()) {
111-
return;
107+
if (isset($this->entries[$this->position])) {
108+
return true;
112109
}
113110

114-
if (contains_key($this->entries, $this->position + 1)) {
115-
return;
111+
if (null !== $this->generator && $this->generator->valid()) {
112+
return true;
116113
}
117114

118-
if (!$this->saved) {
119-
$this->progress();
120-
}
121-
$this->saved = false;
122-
$this->generator?->next();
115+
$this->generator = null;
116+
return false;
117+
}
123118

124-
$this->progress();
119+
private function save(): void
120+
{
121+
if (!$this->saved && $this->generator !== null) {
122+
$this->saved = true;
123+
$this->entries[] = [$this->generator->key(), $this->generator->current()];
124+
}
125125
}
126126

127127
/**
@@ -134,34 +134,11 @@ public function next(): void
134134
public function key(): mixed
135135
{
136136
Psl\invariant($this->valid(), 'The Iterator is invalid.');
137-
if (!contains_key($this->entries, $this->position)) {
138-
$this->progress();
139-
}
137+
$this->save();
140138

141139
return $this->entries[$this->position][0];
142140
}
143141

144-
/**
145-
* Checks if current position is valid.
146-
*/
147-
public function valid(): bool
148-
{
149-
if (contains_key($this->entries, $this->position)) {
150-
return true;
151-
}
152-
153-
if (null === $this->generator) {
154-
return false;
155-
}
156-
157-
if ($this->generator->valid()) {
158-
return true;
159-
}
160-
161-
$this->generator = null;
162-
return false;
163-
}
164-
165142
/**
166143
* Rewind the Iterator to the first element.
167144
*/
@@ -179,16 +156,18 @@ public function rewind(): void
179156
*/
180157
public function seek(int $position): void
181158
{
182-
if (0 === $position || $position <= $this->position) {
159+
if ($position <= $this->position) {
183160
$this->position = $position;
184161
return;
185162
}
186163

187164
if ($this->generator) {
188-
while ($this->position !== $position) {
165+
do {
166+
$this->save();
189167
$this->next();
190-
Psl\invariant($this->valid(), 'Position is out-of-bounds.');
191-
}
168+
/** @psalm-suppress PossiblyNullReference - ->next() and ->save() don't mutate ->generator. */
169+
Psl\invariant($this->generator->valid(), 'Position is out-of-bounds.');
170+
} while ($this->position < $position);
192171

193172
return;
194173
}
@@ -199,43 +178,39 @@ public function seek(int $position): void
199178
}
200179

201180
/**
202-
* @return 0|positive-int
203-
*
204-
* @psalm-suppress MoreSpecificReturnType
181+
* Move forward to the next element.
205182
*/
206-
public function count(): int
183+
public function next(): void
207184
{
208-
if ($this->generator) {
209-
$this->exhaust();
185+
$this->position++;
186+
187+
if (isset($this->entries[$this->position]) || null === $this->generator || !$this->generator->valid()) {
188+
return;
210189
}
211190

212-
/**
213-
* @psalm-suppress LessSpecificReturnStatement
214-
*/
215-
return count($this->entries);
191+
$this->generator->next();
192+
$this->saved = false;
216193
}
217194

218-
private function exhaust(): void
195+
/**
196+
* @return int<0, max>
197+
*
198+
* @psalm-suppress PossiblyNullReference
199+
*/
200+
public function count(): int
219201
{
220202
if ($this->generator) {
221-
if ($this->generator->valid()) {
222-
foreach ($this->generator as $key => $value) {
223-
$this->entries[] = [$key, $value];
224-
}
225-
}
203+
$previous = $this->position;
204+
do {
205+
$this->save();
206+
$this->next();
207+
} while ($this->generator->valid());
208+
$this->position = $previous;
226209

227210
$this->generator = null;
228211
}
229-
}
230212

231-
/**
232-
* Save the current key and value to the local entries if the generator is still valid.
233-
*/
234-
private function progress(): void
235-
{
236-
if ($this->generator && $this->generator->valid() && !$this->saved) {
237-
$this->entries[] = [$this->generator->key(), $this->generator->current()];
238-
$this->saved = true;
239-
}
213+
/** @var int<0, max> */
214+
return count($this->entries);
240215
}
241216
}

tests/unit/Iter/IteratorTest.php

+33
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,39 @@ public function testIterating(): void
129129
], $spy->toArray());
130130
}
131131

132+
public function testCountWhileIterating(): void
133+
{
134+
$spy = new MutableVector([]);
135+
136+
$generator = (static function () use ($spy): iterable {
137+
for ($i = 0; $i < 3; $i++) {
138+
$spy->add('sending (' . $i . ')');
139+
140+
yield ['foo', 'bar'] => $i;
141+
}
142+
})();
143+
144+
$rewindable = Iter\rewindable($generator);
145+
foreach ($rewindable as $key => $value) {
146+
$spy->add('count (' . $rewindable->count() . ')');
147+
$spy->add('received (' . $value . ')');
148+
149+
static::assertSame(['foo', 'bar'], $key);
150+
}
151+
152+
static::assertSame([
153+
'sending (0)',
154+
'sending (1)',
155+
'sending (2)',
156+
'count (3)',
157+
'received (0)',
158+
'count (3)',
159+
'received (1)',
160+
'count (3)',
161+
'received (2)',
162+
], $spy->toArray());
163+
}
164+
132165
public function testRewindingValidGenerator(): void
133166
{
134167
$spy = new MutableVector([]);

0 commit comments

Comments
 (0)