mirror of
https://github.com/danog/endtoend-test-psl.git
synced 2024-11-30 04:39:48 +01:00
[RandomSequence] Introduce random sequence component
Signed-off-by: azjezz <azjezz@protonmail.com>
This commit is contained in:
parent
87e0a73c00
commit
0d1d182576
@ -27,6 +27,7 @@
|
||||
- [Psl\Observer](./component/observer.md)
|
||||
- [Psl\Password](./component/password.md)
|
||||
- [Psl\PseudoRandom](./component/pseudo-random.md)
|
||||
- [Psl\RandomSequence](./component/random-sequence.md)
|
||||
- [Psl\Regex](./component/regex.md)
|
||||
- [Psl\Result](./component/result.md)
|
||||
- [Psl\SecureRandom](./component/secure-random.md)
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
- [invariant](./../../src/Psl/invariant.php#L18)
|
||||
- [invariant_violation](./../../src/Psl/invariant_violation.php#L16)
|
||||
- [sequence](./../../src/Psl/sequence.php#L16)
|
||||
- [sequence](./../../src/Psl/sequence.php#L18)
|
||||
|
||||
#### `Classes`
|
||||
|
||||
|
23
docs/component/random-sequence.md
Normal file
23
docs/component/random-sequence.md
Normal file
@ -0,0 +1,23 @@
|
||||
<!--
|
||||
This markdown file was generated using `docs/documenter.php`.
|
||||
|
||||
Any edits to it will likely be lost.
|
||||
-->
|
||||
|
||||
[*index](./../README.md)
|
||||
|
||||
---
|
||||
|
||||
### `Psl\RandomSequence` Component
|
||||
|
||||
#### `Interfaces`
|
||||
|
||||
- [SequenceInterface](./../../src/Psl/RandomSequence/SequenceInterface.php#L7)
|
||||
|
||||
#### `Classes`
|
||||
|
||||
- [MersenneTwisterPHPVariantSequence](./../../src/Psl/RandomSequence/MersenneTwisterPHPVariantSequence.php#L10)
|
||||
- [MersenneTwisterSequence](./../../src/Psl/RandomSequence/MersenneTwisterSequence.php#L10)
|
||||
- [SecureSequence](./../../src/Psl/RandomSequence/SecureSequence.php#L12)
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
- [bytes](./../../src/Psl/SecureRandom/bytes.php#L20)
|
||||
- [float](./../../src/Psl/SecureRandom/float.php#L14)
|
||||
- [int](./../../src/Psl/SecureRandom/int.php#L21)
|
||||
- [int](./../../src/Psl/SecureRandom/int.php#L23)
|
||||
- [string](./../../src/Psl/SecureRandom/string.php#L25)
|
||||
|
||||
|
||||
|
@ -201,6 +201,7 @@ function get_all_components(): array
|
||||
'Psl\\Observer',
|
||||
'Psl\\Password',
|
||||
'Psl\\PseudoRandom',
|
||||
'Psl\\RandomSequence',
|
||||
'Psl\\Regex',
|
||||
'Psl\\Result',
|
||||
'Psl\\SecureRandom',
|
||||
|
@ -579,9 +579,12 @@ final class Loader
|
||||
'Psl\IO\SeekReadWriteHandleInterface',
|
||||
'Psl\IO\SeekWriteHandleInterface',
|
||||
'Psl\IO\WriteHandleInterface',
|
||||
'Psl\RandomSequence\SequenceInterface',
|
||||
];
|
||||
|
||||
public const TRAITS = [];
|
||||
public const TRAITS = [
|
||||
'Psl\RandomSequence\Internal\MersenneTwisterTrait',
|
||||
];
|
||||
|
||||
public const CLASSES = [
|
||||
'Psl\Ref',
|
||||
@ -658,6 +661,9 @@ final class Loader
|
||||
'Psl\IO\Reader',
|
||||
'Psl\IO\MemoryHandle',
|
||||
'Psl\Fun\Internal\LazyEvaluator',
|
||||
'Psl\RandomSequence\MersenneTwisterSequence',
|
||||
'Psl\RandomSequence\MersenneTwisterPHPVariantSequence',
|
||||
'Psl\RandomSequence\SecureSequence',
|
||||
];
|
||||
|
||||
public const TYPE_CONSTANTS = 1;
|
||||
|
75
src/Psl/RandomSequence/Internal/MersenneTwisterTrait.php
Normal file
75
src/Psl/RandomSequence/Internal/MersenneTwisterTrait.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Psl\RandomSequence\Internal;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
trait MersenneTwisterTrait
|
||||
{
|
||||
/**
|
||||
* @var array<int, int>
|
||||
*/
|
||||
private array $state;
|
||||
|
||||
private int $index;
|
||||
|
||||
final public function __construct(
|
||||
int $seed
|
||||
) {
|
||||
$state = [$seed & 0xffffffff];
|
||||
/** @var array{0: int, 1: int} $i */
|
||||
$i = [$seed & 0xffff, ($seed >> 16) & 0xffff];
|
||||
|
||||
for ($index = 1; $index < 624; $index++) {
|
||||
$i[0] ^= $i[1] >> 14;
|
||||
|
||||
$carry = (0x8965 * $i[0]) + $index;
|
||||
$i[1] = ((0x8965 * $i[1]) + (0x6C07 * $i[0]) + ($carry >> 16)) & 0xffff;
|
||||
$i[0] = $carry & 0xffff;
|
||||
|
||||
$state[$index] = ($i[1] << 16) | $i[0];
|
||||
}
|
||||
|
||||
$this->state = $state;
|
||||
$this->index = $index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the next pseudorandom number.
|
||||
*
|
||||
* @psalm-external-mutation-free
|
||||
*/
|
||||
final public function next(): int
|
||||
{
|
||||
if ($this->index >= 624) {
|
||||
$state = $this->state;
|
||||
for ($i = 0; $i < 227; $i++) {
|
||||
$state[$i] = $this->twist($state[$i + 397], $state[$i], $state[$i + 1]);
|
||||
}
|
||||
for (; $i < 623; $i++) {
|
||||
$state[$i] = $this->twist($state[$i - 227], $state[$i], $state[$i + 1]);
|
||||
}
|
||||
$state[623] = $this->twist($state[396], $state[623], $state[0]);
|
||||
$this->state = $state;
|
||||
|
||||
$this->index = 0;
|
||||
}
|
||||
|
||||
$y = $this->state[$this->index++];
|
||||
|
||||
$y ^= ($y >> 11) & 0x001fffff;
|
||||
$y ^= ($y << 7) & 0x9d2c5680;
|
||||
$y ^= ($y << 15) & 0xefc60000;
|
||||
$y ^= ($y >> 18) & 0x00003fff;
|
||||
|
||||
return ($y >> 1) & 0x7fffffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* @pure
|
||||
*/
|
||||
abstract protected function twist(int $m, int $u, int $v): int;
|
||||
}
|
21
src/Psl/RandomSequence/MersenneTwisterPHPVariantSequence.php
Normal file
21
src/Psl/RandomSequence/MersenneTwisterPHPVariantSequence.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Psl\RandomSequence;
|
||||
|
||||
/**
|
||||
* A PRNG Based on the PHP variant of Mersenne Twister Algorithm.
|
||||
*/
|
||||
final class MersenneTwisterPHPVariantSequence implements SequenceInterface
|
||||
{
|
||||
use Internal\MersenneTwisterTrait;
|
||||
|
||||
/**
|
||||
* @pure
|
||||
*/
|
||||
protected function twist(int $m, int $u, int $v): int
|
||||
{
|
||||
return $m ^ (((($u & 0x80000000) | ($v & 0x7fffffff)) >> 1) & 0x7fffffff) ^ (0x9908b0df * ($u & 1));
|
||||
}
|
||||
}
|
21
src/Psl/RandomSequence/MersenneTwisterSequence.php
Normal file
21
src/Psl/RandomSequence/MersenneTwisterSequence.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Psl\RandomSequence;
|
||||
|
||||
/**
|
||||
* A Mersenne Twister ( MT19937 ) PRNG.
|
||||
*/
|
||||
final class MersenneTwisterSequence implements SequenceInterface
|
||||
{
|
||||
use Internal\MersenneTwisterTrait;
|
||||
|
||||
/**
|
||||
* @pure
|
||||
*/
|
||||
protected function twist(int $m, int $u, int $v): int
|
||||
{
|
||||
return $m ^ (((($u & 0x80000000) | ($v & 0x7fffffff)) >> 1) & 0x7fffffff) ^ (0x9908b0df * ($v & 1));
|
||||
}
|
||||
}
|
22
src/Psl/RandomSequence/SecureSequence.php
Normal file
22
src/Psl/RandomSequence/SecureSequence.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Psl\RandomSequence;
|
||||
|
||||
use Psl\SecureRandom;
|
||||
|
||||
/**
|
||||
* A Cryptographically Secure PRNG.
|
||||
*/
|
||||
final class SecureSequence implements SequenceInterface
|
||||
{
|
||||
/**
|
||||
* Generates the next pseudorandom number.
|
||||
*/
|
||||
public function next(): int
|
||||
{
|
||||
/** @psalm-suppress MissingThrowsDocblock */
|
||||
return SecureRandom\int();
|
||||
}
|
||||
}
|
13
src/Psl/RandomSequence/SequenceInterface.php
Normal file
13
src/Psl/RandomSequence/SequenceInterface.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Psl\RandomSequence;
|
||||
|
||||
interface SequenceInterface
|
||||
{
|
||||
/**
|
||||
* Generates the next pseudorandom number.
|
||||
*/
|
||||
public function next(): int;
|
||||
}
|
@ -17,6 +17,8 @@ use function random_int;
|
||||
*
|
||||
* @throws Exception\InsufficientEntropyException If it was not possible to gather sufficient entropy.
|
||||
* @throws Psl\Exception\InvariantViolationException If $min > $max.
|
||||
*
|
||||
* @psalm-external-mutation-free
|
||||
*/
|
||||
function int(int $min = Math\INT64_MIN, int $max = Math\INT64_MAX): int
|
||||
{
|
||||
|
@ -12,8 +12,15 @@ namespace Psl;
|
||||
* @param T ...$args
|
||||
*
|
||||
* @return T|null
|
||||
*
|
||||
* @pure
|
||||
*/
|
||||
function sequence(...$args)
|
||||
{
|
||||
return Iter\last($args);
|
||||
$result = null;
|
||||
foreach ($args as $arg) {
|
||||
$result = $arg;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Psl\Tests\Unit\RandomSequence;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psl\RandomSequence\MersenneTwisterPHPVariantSequence;
|
||||
use Psl\SecureRandom;
|
||||
|
||||
use function mt_rand;
|
||||
use function mt_srand;
|
||||
use function time;
|
||||
|
||||
use const MT_RAND_PHP;
|
||||
|
||||
final class MersenneTwisterPHPVariantSequenceTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideSeeds
|
||||
*/
|
||||
public function testNext(int $seed): void
|
||||
{
|
||||
mt_srand($seed, MT_RAND_PHP);
|
||||
$sequence = new MersenneTwisterPHPVariantSequence($seed);
|
||||
|
||||
for ($i = 0; $i < 100; $i++) {
|
||||
static::assertSame(mt_rand(), $sequence->next());
|
||||
}
|
||||
}
|
||||
|
||||
public function provideSeeds(): iterable
|
||||
{
|
||||
yield [45635];
|
||||
yield [5744];
|
||||
yield [456];
|
||||
yield [34];
|
||||
yield [5];
|
||||
yield [time()];
|
||||
yield [SecureRandom\int()];
|
||||
}
|
||||
}
|
42
tests/unit/RandomSequence/MersenneTwisterSequenceTest.php
Normal file
42
tests/unit/RandomSequence/MersenneTwisterSequenceTest.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Psl\Tests\Unit\RandomSequence;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psl\RandomSequence\MersenneTwisterSequence;
|
||||
use Psl\SecureRandom;
|
||||
|
||||
use function mt_rand;
|
||||
use function mt_srand;
|
||||
use function time;
|
||||
|
||||
use const MT_RAND_MT19937;
|
||||
|
||||
final class MersenneTwisterSequenceTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideSeeds
|
||||
*/
|
||||
public function testNext(int $seed): void
|
||||
{
|
||||
mt_srand($seed, MT_RAND_MT19937);
|
||||
$sequence = new MersenneTwisterSequence($seed);
|
||||
|
||||
for ($i = 0; $i < 100; $i++) {
|
||||
static::assertSame(mt_rand(), $sequence->next());
|
||||
}
|
||||
}
|
||||
|
||||
public function provideSeeds(): iterable
|
||||
{
|
||||
yield [45635];
|
||||
yield [5744];
|
||||
yield [456];
|
||||
yield [34];
|
||||
yield [5];
|
||||
yield [time()];
|
||||
yield [SecureRandom\int()];
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user