Skip to content

Commit

Permalink
refactor: Rewrite iterators and optimize things here and there.
Browse files Browse the repository at this point in the history
  • Loading branch information
drupol committed Dec 17, 2020
1 parent a6e12ed commit 87124da
Show file tree
Hide file tree
Showing 12 changed files with 300 additions and 99 deletions.
50 changes: 50 additions & 0 deletions spec/loophp/collection/Iterator/ArrayCacheIteratorSpec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

namespace spec\loophp\collection\Iterator;

use ArrayIterator;
use loophp\collection\Iterator\ArrayCacheIterator;
use PhpSpec\Exception\Exception;
use PhpSpec\ObjectBehavior;

class ArrayCacheIteratorSpec extends ObjectBehavior
{
public function it_can_cache_an_iterator_of_type_generator()
{
$generator = static function () {
yield 'a';

yield 'b';

yield 'c';

yield 'd';

yield 'e';
};

$this->beConstructedWith($generator());

$this
->valid()
->shouldReturn(true);

if (5 !== iterator_count($this->getWrappedObject())) {
throw new Exception('The count is invalid.');
}

$this
->shouldIterateAs(
range('a', 'e')
);
}

public function it_is_initializable()
{
$this->beConstructedWith(new ArrayIterator([]));

$this->shouldHaveType(ArrayCacheIterator::class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@

use ArrayIterator;
use Iterator;
use loophp\collection\Iterator\CacheIterator;
use loophp\collection\Iterator\PsrCacheIterator;
use PhpSpec\ObjectBehavior;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\CacheItem;

class CacheIteratorSpec extends ObjectBehavior
class PsrCacheIteratorSpec extends ObjectBehavior
{
public function it_can_cache_data(CacheItemPoolInterface $cache)
{
Expand Down Expand Up @@ -88,6 +88,6 @@ public function it_can_get_the_inner_iterator(Iterator $iterator, CacheItemPoolI
public function it_is_initializable(Iterator $iterator, CacheItemPoolInterface $cache)
{
$this->beConstructedWith($iterator, $cache);
$this->shouldHaveType(CacheIterator::class);
$this->shouldHaveType(PsrCacheIterator::class);
}
}
48 changes: 27 additions & 21 deletions spec/loophp/collection/Iterator/RandomIteratorSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,39 +23,39 @@ public function it_can_build_an_iterator_with_a_random_seed()

$expected = [
2 => 'c',
20 => 'u',
8 => 'i',
3 => 'd',
7 => 'h',
9 => 'j',
0 => 'a',
21 => 'v',
12 => 'm',
15 => 'p',
13 => 'n',
4 => 'e',
19 => 't',
10 => 'k',
22 => 'w',
21 => 'v',
20 => 'u',
14 => 'o',
11 => 'l',
1 => 'b',
5 => 'f',
18 => 's',
23 => 'x',
17 => 'r',
25 => 'z',
24 => 'y',
10 => 'k',
13 => 'n',
23 => 'x',
15 => 'p',
8 => 'i',
11 => 'l',
0 => 'a',
7 => 'h',
19 => 't',
9 => 'j',
3 => 'd',
16 => 'q',
18 => 's',
1 => 'b',
12 => 'm',
25 => 'z',
14 => 'o',
6 => 'g',
];

if (iterator_to_array($this->getWrappedObject()) !== $expected) {
throw new Exception('Iterator is not equal to the expected array.');
}

$iterator1 = new RandomIterator($input, $seed);
$iterator2 = new RandomIterator($input, $seed + $seed);
$iterator1 = new RandomIterator(new ArrayIterator(range('a', 'z')), $seed);
$iterator2 = new RandomIterator(new ArrayIterator(range('a', 'z')), $seed + $seed);

if (iterator_to_array($iterator1) === iterator_to_array($iterator2)) {
throw new Exception('Iterator1 is equal to Iterator2');
Expand All @@ -67,7 +67,7 @@ public function it_can_build_an_iterator_without_a_random_seed()
$input = new ArrayIterator(range('a', 'z'));
$this->beConstructedWith($input);

$iterator1 = new RandomIterator($input);
$iterator1 = new RandomIterator(new ArrayIterator(range('a', 'z')));

if (iterator_to_array($iterator1) === iterator_to_array($this->getWrappedObject())) {
throw new Exception('Iterator1 is equal to Iterator2');
Expand All @@ -85,6 +85,12 @@ public function it_can_rewind()

$this->beConstructedWith($iterator, 1);

$this
->valid()
->shouldReturn(false);

$this->rewind();

$this
->valid()
->shouldReturn(true);
Expand Down
10 changes: 3 additions & 7 deletions src/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use IteratorIterator;
use loophp\collection\Contract\Collection as CollectionInterface;
use loophp\collection\Contract\Operation;
use loophp\collection\Iterator\ClosureIterator;
use loophp\collection\Iterator\IterableIterator;
use loophp\collection\Iterator\ResourceIterator;
use loophp\collection\Iterator\StringIterator;
Expand Down Expand Up @@ -425,10 +426,7 @@ public function get($key, $default = null): CollectionInterface

public function getIterator(): Iterator
{
$iterator = new IteratorIterator(($this->source)(...$this->parameters));
$iterator->rewind();

return $iterator;
return new ClosureIterator($this->source, ...$this->parameters);
}

public function group(): CollectionInterface
Expand Down Expand Up @@ -657,9 +655,7 @@ public function scanRight1(callable $callback): CollectionInterface

public function shuffle(?int $seed = null): CollectionInterface
{
if (null === $seed) {
$seed = random_int(PHP_INT_MIN, PHP_INT_MAX);
}
$seed ??= random_int(PHP_INT_MIN, PHP_INT_MAX);

return new self(Shuffle::of()($seed), $this->getIterator());
}
Expand Down
93 changes: 93 additions & 0 deletions src/Iterator/ArrayCacheIterator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

declare(strict_types=1);

namespace loophp\collection\Iterator;

use Iterator;

use function array_key_exists;

/**
* @internal
*
* @psalm-template TKey
* @psalm-template TKey of array-key
* @psalm-template T
*
* @extends ProxyIterator<TKey, T>
*/
final class ArrayCacheIterator extends ProxyIterator
{
/**
* @psalm-var array<int, array{0: TKey, 1: T}>
*/
private array $cache = [];

private int $key = 0;

/**
* @psalm-param Iterator<TKey, T> $iterator
*/
public function __construct(Iterator $iterator)
{
$this->iterator = $iterator;
}

/**
* @psalm-return T
*/
public function current()
{
/** @psalm-var array{TKey, T} $data */
$data = $this->getTupleFromCache($this->key);

return $data[1];
}

/**
* @psalm-return TKey
*/
public function key()
{
/** @psalm-var array{TKey, T} $data */
$data = $this->getTupleFromCache($this->key);

return $data[0];
}

public function next(): void
{
// This is mostly for iterator_count().
$this->getTupleFromCache($this->key++);

parent::next();
}

public function rewind(): void
{
// No call to parent::rewind() because we do not know if the inner
// iterator can be rewinded or not.
$this->key = 0;
}

public function valid(): bool
{
if (parent::valid()) {
return true;
}

return array_key_exists($this->key, $this->cache);
}

/**
* @psalm-return array{0: TKey, 1: T}
*/
private function getTupleFromCache(int $key): array
{
return $this->cache[$key] ??= [
parent::key(),
parent::current(),
];
}
}
53 changes: 53 additions & 0 deletions src/Iterator/ClosureIterator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

namespace loophp\collection\Iterator;

use Generator;

/**
* @psalm-template TKey
* @psalm-template T
*
* @extends ProxyIterator<TKey, T>
*/
final class ClosureIterator extends ProxyIterator
{
/**
* @var array<int, mixed>
* @psalm-var list<mixed>
*/
private array $arguments;

/**
* @var callable
* @psalm-var callable(mixed ...):Generator<TKey, T>
*/
private $callable;

/**
* @param mixed ...$arguments
* @psalm-param mixed ...$arguments
* @psalm-param callable(mixed ...):Generator<TKey, T> $callable
*/
public function __construct(callable $callable, ...$arguments)
{
$this->callable = $callable;
$this->arguments = $arguments;
$this->iterator = $this->getGenerator();
}

public function rewind(): void
{
$this->iterator = $this->getGenerator();
}

/**
* @psalm-return Generator<TKey, T>
*/
private function getGenerator(): Generator
{
return yield from ($this->callable)(...$this->arguments);
}
}
23 changes: 12 additions & 11 deletions src/Iterator/IterableIterator.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@

namespace loophp\collection\Iterator;

use ArrayIterator;
use IteratorIterator;

use function is_array;
use Generator;

/**
* @psalm-template TKey
Expand All @@ -23,12 +20,16 @@ final class IterableIterator extends ProxyIterator
*/
public function __construct(iterable $iterable)
{
if (is_array($iterable)) {
$iterable = new ArrayIterator($iterable);
}

$this->iterator = new IteratorIterator($iterable);

$this->rewind();
$this->iterator = new ClosureIterator(
/**
* @psalm-param iterable<TKey, T> $iterable
*/
static function (iterable $iterable): Generator {
foreach ($iterable as $key => $value) {
yield $key => $value;
}
},
$iterable
);
}
}
Loading

0 comments on commit 87124da

Please sign in to comment.