diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..638ef6d --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,16 @@ +parameters: + ignoreErrors: + - + message: "#^While loop condition is always true\\.$#" + count: 1 + path: src/RandomIntegerAggregate.php + + - + message: "#^Parameter \\#1 \\$iterable of class loophp\\\\iterators\\\\UnpackIterableAggregate constructor expects iterable\\<\\(int\\|string\\), array\\{TKey, T\\}\\>, loophp\\\\iterators\\\\UnpackIterableAggregate\\ given\\.$#" + count: 1 + path: src/RandomIterableAggregate.php + + - + message: "#^Parameter \\#1 \\$iterable of class loophp\\\\iterators\\\\UnpackIterableAggregate constructor expects iterable\\<\\(int\\|string\\), array\\{array\\{TKey, T\\}\\|int\\|null, array\\{TKey, T\\}\\|int\\|null\\}\\>, loophp\\\\iterators\\\\SortIterableAggregate\\, array\\\\> given\\.$#" + count: 1 + path: src/RandomIterableAggregate.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..2ee6a55 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,2 @@ +includes: + - phpstan-baseline.neon diff --git a/src/RandomIntegerAggregate.php b/src/RandomIntegerAggregate.php new file mode 100644 index 0000000..c140fe4 --- /dev/null +++ b/src/RandomIntegerAggregate.php @@ -0,0 +1,36 @@ + + */ +final class RandomIntegerAggregate implements IteratorAggregate +{ + public function __construct( + private readonly ?int $seed = null, + private readonly int $min = 0, + private readonly int $max = PHP_INT_MAX, + ) {} + + /** + * @return Generator + */ + public function getIterator(): Generator + { + if (null !== $this->seed) { + mt_srand($this->seed); + } + + while (true) { + yield mt_rand($this->min, $this->max); + } + } +} diff --git a/src/RandomIterableAggregate.php b/src/RandomIterableAggregate.php index e52f884..2b47eb3 100644 --- a/src/RandomIterableAggregate.php +++ b/src/RandomIterableAggregate.php @@ -15,50 +15,31 @@ */ final class RandomIterableAggregate implements IteratorAggregate { - /** - * @var PackIterableAggregate - */ - private PackIterableAggregate $iteratorAggregate; - /** * @param iterable $iterable */ - public function __construct(iterable $iterable, private int $seed = 0) - { - $this->iteratorAggregate = new PackIterableAggregate($iterable); - } + public function __construct( + private readonly iterable $iterable, + private readonly ?int $seed = null + ) {} /** * @return Generator - */ - public function getIterator(): Generator - { - mt_srand($this->seed); - - yield from $this->randomize($this->iteratorAggregate, $this->seed); - } - - /** - * @param iterable $iterable * - * @return Generator + * @psalm-suppress InvalidArgument */ - private function randomize(iterable $iterable, int $seed): Generator + public function getIterator(): Generator { - $queue = []; - - foreach (new UnpackIterableAggregate($iterable) as $key => $value) { - if (0 === mt_rand(0, $seed)) { - yield $key => $value; - - continue; - } - - $queue[] = [$key, $value]; - } - - if ([] !== $queue) { - yield from $this->randomize($queue, $seed); - } + yield from new UnpackIterableAggregate( + new UnpackIterableAggregate( + new SortIterableAggregate( + new MultipleIterableAggregate([ + new RandomIntegerAggregate($this->seed), + new PackIterableAggregate($this->iterable), + ]), + static fn (array $a, array $b): int => $a[0] <=> $b[0] + ) + ) + ); } } diff --git a/src/SortIterableAggregate.php b/src/SortIterableAggregate.php index 4644f7c..7b7f94d 100644 --- a/src/SortIterableAggregate.php +++ b/src/SortIterableAggregate.php @@ -21,7 +21,10 @@ final class SortIterableAggregate implements IteratorAggregate * @param iterable $iterable * @param Closure(T, T, TKey, TKey): int $callback */ - public function __construct(private readonly iterable $iterable, private readonly Closure $callback) {} + public function __construct( + private readonly iterable $iterable, + private readonly Closure $callback + ) {} /** * @return Generator diff --git a/tests/unit/RandomIterableAggregateTest.php b/tests/unit/RandomIterableAggregateTest.php index 36d400f..483d23e 100644 --- a/tests/unit/RandomIterableAggregateTest.php +++ b/tests/unit/RandomIterableAggregateTest.php @@ -15,6 +15,8 @@ use function count; +use const PHP_VERSION_ID; + /** * @internal * @@ -22,13 +24,17 @@ */ final class RandomIterableAggregateTest extends TestCase { - public function testSeed(): void + public function testSeedWithPHP81AndSmaller(): void { + if (PHP_VERSION_ID >= 80200) { + self::markTestSkipped('This test is only for PHP 8.1 and smaller.'); + } + $input = static function (): Generator { yield from array_combine(range('a', 'z'), range('a', 'z')); }; - $iterator = (new RandomIterableAggregate($input())); + $iterator = (new RandomIterableAggregate($input(), 0)); $a = []; @@ -36,14 +42,96 @@ public function testSeed(): void $a[$key] = $value; } - $expected = array_combine(range('a', 'z'), range('a', 'z')); + $expected = [ + 'q' => 'q', + 'l' => 'l', + 'd' => 'd', + 'a' => 'a', + 'm' => 'm', + 'o' => 'o', + 'p' => 'p', + 'c' => 'c', + 'y' => 'y', + 'z' => 'z', + 'f' => 'f', + 'b' => 'b', + 's' => 's', + 'x' => 'x', + 'k' => 'k', + 'v' => 'v', + 'r' => 'r', + 't' => 't', + 'j' => 'j', + 'h' => 'h', + 'e' => 'e', + 'n' => 'n', + 'g' => 'g', + 'w' => 'w', + 'i' => 'i', + 'u' => 'u', + ]; self::assertSame(iterator_count($input()), count($expected)); self::assertSame($expected, $a); } - public function testSimple(): void + public function testSeedWithPHP82AndGreater(): void { + if (PHP_VERSION_ID < 80200) { + self::markTestSkipped('This test is only for PHP 8.1 and smaller.'); + } + + $input = static function (): Generator { + yield from array_combine(range('a', 'z'), range('a', 'z')); + }; + + $iterator = (new RandomIterableAggregate($input(), 0)); + + $a = []; + + foreach ($iterator as $key => $value) { + $a[$key] = $value; + } + + $expected = [ + 'w' => 'w', + 'h' => 'h', + 'z' => 'z', + 'a' => 'a', + 'e' => 'e', + 's' => 's', + 'p' => 'p', + 'x' => 'x', + 'y' => 'y', + 'i' => 'i', + 'g' => 'g', + 'v' => 'v', + 'k' => 'k', + 'n' => 'n', + 'o' => 'o', + 'b' => 'b', + 'd' => 'd', + 'c' => 'c', + 'q' => 'q', + 't' => 't', + 'f' => 'f', + 'm' => 'm', + 'r' => 'r', + 'u' => 'u', + 'j' => 'j', + 'l' => 'l', + ]; + + self::assertSame(iterator_count($input()), count($expected)); + self::assertSame($expected, $a); + } + + public function testSimpleWithPHP81AndSmaller(): void + { + if (PHP_VERSION_ID >= 80200) { + self::markTestSkipped('This test is only for PHP 8.1 and smaller.'); + } + $input = static function (): Generator { yield from array_combine(range('a', 'f'), range('a', 'f')); }; @@ -59,12 +147,45 @@ public function testSimple(): void } $expected = [ - ['a', 'a'], ['b', 'b'], + ['c', 'c'], + ['f', 'f'], + ['e', 'e'], ['d', 'd'], + ['a', 'a'], + ]; + + self::assertSame(iterator_count($input()), count($expected)); + self::assertSame($expected, $a); + } + + public function testSimpleWithPHP82AndGreater(): void + { + if (PHP_VERSION_ID < 80200) { + self::markTestSkipped('This test is only for PHP 8.2 and greater.'); + } + + $input = static function (): Generator { + yield from array_combine(range('a', 'f'), range('a', 'f')); + }; + + $seed = 2; + + $iterator = (new RandomIterableAggregate($input(), $seed)); + + $a = []; + + foreach ($iterator as $key => $value) { + $a[] = [$key, $value]; + } + + $expected = [ ['f', 'f'], - ['c', 'c'], + ['a', 'a'], ['e', 'e'], + ['b', 'b'], + ['c', 'c'], + ['d', 'd'], ]; self::assertSame(iterator_count($input()), count($expected));