Skip to content

Commit

Permalink
refactor: Add a $seed parameter to RandomIterator.
Browse files Browse the repository at this point in the history
  • Loading branch information
drupol committed Dec 6, 2020
1 parent bbf0045 commit be7a0c1
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 26 deletions.
68 changes: 66 additions & 2 deletions spec/loophp/collection/Iterator/RandomIteratorSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,75 @@
namespace spec\loophp\collection\Iterator;

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

class RandomIteratorSpec extends ObjectBehavior
{
public function it_can_build_an_iterator_with_a_random_seed()
{
$input = new ArrayIterator(range('a', 'z'));
$seed = 123;

$this->beConstructedWith(
$input,
$seed
);

$expected = [
2 => 'c',
4 => 'e',
22 => 'w',
21 => 'v',
20 => 'u',
14 => 'o',
5 => 'f',
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',
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);

if (iterator_to_array($iterator1) === iterator_to_array($iterator2)) {
throw new Exception('Iterator1 is equal to Iterator2');
}
}

public function it_can_build_an_iterator_without_a_random_seed()
{
$input = new ArrayIterator(range('a', 'z'));
$this->beConstructedWith($input);

$iterator1 = new RandomIterator($input);

if (iterator_to_array($iterator1) === iterator_to_array($this->getWrappedObject())) {
throw new Exception('Iterator1 is equal to Iterator2');
}
}

public function it_can_get_the_innerIterator()
{
$this->getInnerIterator()->shouldBeAnInstanceOf(ArrayIterator::class);
Expand All @@ -19,7 +83,7 @@ public function it_can_rewind()
{
$iterator = new ArrayIterator(['a']);

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

$this
->valid()
Expand Down Expand Up @@ -56,6 +120,6 @@ public function let()
{
$iterator = new ArrayIterator(range('a', 'c'));

$this->beConstructedWith($iterator);
$this->beConstructedWith($iterator, 1);
}
}
17 changes: 13 additions & 4 deletions src/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@

use const INF;
use const PHP_INT_MAX;
use const PHP_INT_MIN;

/**
* @psalm-template TKey
Expand Down Expand Up @@ -588,9 +589,13 @@ public function product(iterable ...$iterables): CollectionInterface
return new self(Product::of()(...$iterables), $this->getIterator());
}

public function random(int $size = 1): CollectionInterface
public function random(int $size = 1, ?int $seed = null): CollectionInterface
{
return new self(Random::of()($size), $this->getIterator());
if (null === $seed) {
$seed = random_int(PHP_INT_MIN, PHP_INT_MAX);
}

return new self(Random::of()($seed)($size), $this->getIterator());
}

public static function range(float $start = 0.0, float $end = INF, float $step = 1.0): CollectionInterface
Expand Down Expand Up @@ -643,9 +648,13 @@ public function scanRight1(callable $callback): CollectionInterface
return new self(ScanRight1::of()($callback), $this->getIterator());
}

public function shuffle(): CollectionInterface
public function shuffle(?int $seed = null): CollectionInterface
{
return new self(Shuffle::of(), $this->getIterator());
if (null === $seed) {
$seed = random_int(PHP_INT_MIN, PHP_INT_MAX);
}

return new self(Shuffle::of()($seed), $this->getIterator());
}

public function since(callable ...$callbacks): CollectionInterface
Expand Down
21 changes: 18 additions & 3 deletions src/Iterator/RandomIterator.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
use ArrayIterator;
use Iterator;

use function array_slice;

use const PHP_INT_MAX;
use const PHP_INT_MIN;

/**
* @psalm-template TKey
* @psalm-template TKey of array-key
Expand Down Expand Up @@ -36,12 +41,13 @@ final class RandomIterator extends ProxyIterator
/**
* @psalm-param Iterator<TKey, T> $iterator
*/
public function __construct(Iterator $iterator)
public function __construct(Iterator $iterator, ?int $seed = null)
{
$this->iterator = $iterator;
$this->seed = $seed ?? random_int(PHP_INT_MIN, PHP_INT_MAX);
$this->wrappedIterator = $this->buildArrayIterator($iterator);
$this->indexes = array_keys($this->wrappedIterator->getArrayCopy());
$this->key = array_rand($this->indexes);
$this->key = current($this->customArrayRand($this->indexes, 1, $this->seed));
}

public function current()
Expand All @@ -63,7 +69,7 @@ public function next(): void
unset($this->indexes[$this->key]);

if ($this->valid()) {
$this->key = array_rand($this->indexes);
$this->key = current($this->customArrayRand($this->indexes, 1, $this->seed));
}
}

Expand Down Expand Up @@ -94,4 +100,13 @@ private function buildArrayIterator(Iterator $iterator): ArrayIterator

return $arrayIterator;
}

private function customArrayRand(array $array, int $num, int $seed): array
{
mt_srand($seed);
shuffle($array);
mt_srand();

return array_slice($array, 0, $num);
}
}
26 changes: 14 additions & 12 deletions src/Operation/Random.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,25 @@
final class Random extends AbstractOperation
{
/**
* @psalm-return Closure(int): Closure(Iterator<TKey, T>): Generator<TKey, T>
* @psalm-return Closure(int): Closure(int): Closure(Iterator<TKey, T>): Generator<TKey, T>
*/
public function __invoke(): Closure
{
return
/**
* @psalm-return Closure(Iterator<TKey, T>): Generator<TKey, T>
*/
static function (int $size): Closure {
/** @psalm-var Closure(Iterator<TKey, T>): Generator<TKey, T> $pipe */
$pipe = Pipe::of()(
Shuffle::of(),
Limit::of()($size)(0)
);
static function (int $seed): Closure {
/**
* @psalm-return Closure(Iterator<TKey, T>): Generator<TKey, T>
*/
return static function (int $size) use ($seed): Closure {
/** @psalm-var Closure(Iterator<TKey, T>): Generator<TKey, T> $pipe */
$pipe = Pipe::of()(
Shuffle::of()($seed),
Limit::of()($size)(0)
);

// Point free style.
return $pipe;
// Point free style.
return $pipe;
};
};
}
}
16 changes: 11 additions & 5 deletions src/Operation/Shuffle.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,22 @@
final class Shuffle extends AbstractOperation
{
/**
* @psalm-return Closure(Iterator<TKey, T>): Generator<TKey, T, mixed, void>
* @psalm-return Closure(int): Closure(Iterator<TKey, T>): Generator<TKey, T, mixed, void>
*/
public function __invoke(): Closure
{
return
/**
* @psalm-param Iterator<TKey, T> $iterator
*
* @psalm-return Generator<TKey, T, mixed, void>
* @return Closure(Iterator<TKey, T>): Generator<TKey, T, mixed, void>
*/
static fn (Iterator $iterator): Generator => yield from new RandomIterator($iterator);
static function (int $seed): Closure {
return
/**
* @psalm-param Iterator<TKey, T> $iterator
*
* @psalm-return Generator<TKey, T, mixed, void>
*/
static fn (Iterator $iterator): Generator => yield from new RandomIterator($iterator, $seed);
};
}
}

0 comments on commit be7a0c1

Please sign in to comment.