[RandomSequence] Introduce random sequence component

Signed-off-by: azjezz <azjezz@protonmail.com>
This commit is contained in:
azjezz 2021-05-15 23:00:25 +01:00 committed by Saif Eddin Gmati
parent 87e0a73c00
commit 0d1d182576
15 changed files with 280 additions and 4 deletions

View File

@ -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)

View File

@ -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`

View 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)

View File

@ -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)

View File

@ -201,6 +201,7 @@ function get_all_components(): array
'Psl\\Observer',
'Psl\\Password',
'Psl\\PseudoRandom',
'Psl\\RandomSequence',
'Psl\\Regex',
'Psl\\Result',
'Psl\\SecureRandom',

View File

@ -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;

View 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;
}

View 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));
}
}

View 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));
}
}

View 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();
}
}

View File

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Psl\RandomSequence;
interface SequenceInterface
{
/**
* Generates the next pseudorandom number.
*/
public function next(): int;
}

View File

@ -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
{

View File

@ -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;
}

View File

@ -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()];
}
}

View 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()];
}
}