PHP 8.3: Дополнение Randomizer

Источник: «PHP RFC: Randomizer Additions»
Этот RFC предлагает добавить новые методы строительных блоков в \Random\Randomizer, реализующие полезные операции, которые либо многословны, либо очень сложны для реализации в пользовательской среде.

Генерация случайной строки содержащей определённые символы, является распространённым вариантом использования для создания случайных идентификаторов, кодов ваучеров, числовых строк выходящих за диапазон целого числа и многое другое. Реализация этой операции в пользовательской среде требует выбора случайных смещений во входной строке в цикле и нескольких строк кода, что является довольно простой операцией. Также легко ввести тонкие ошибки, например, введя ошибку не вычтенной единицы в отношении максимального индекса строки, забыв вычесть единицу из длинны строки. Очевидная реализация использующая Randomizer::getInt() для выбора смещения, также неэффективна, так как требует хотя бы одного обращения к движку для каждого символа, тогда как 64-битный движок может генерировать случайность для 8 символов одновременно.

Генерация случайного числа с плавающей точкой также является полезным строительным блоком, например, для генерации случайного логического значения с определённым шансом. Правильно сделать это в пользовательской среде находится где-то между нетривиально и невозможно. Очевидная реализация, деление случайного числа, полученного с помощью Randomizer::getInt() на другое целое число приводит к смещению из-за ошибок округления и уменьшения плотности чисел с плавающей точкой для больших абсолютных значений.

Предложение

Добавить три новых метода \Random\Randomizer и перечисление сопровождающие один метод.

namespace Random;

final class Randomizer {
// […]
public function getBytesFromString(string $string, int $length): string {}
public function nextFloat(): float {}
public function getFloat(
float $min,
float $max,
IntervalBoundary $boundary = IntervalBoundary::ClosedOpen
): float {}
}

enum IntervalBoundary {
case ClosedOpen;
case ClosedClosed;
case OpenClosed;
case OpenOpen;
}

getBytesFromString()

Метод позволяющий сгенерировать строку заданной длины, состоящую из случайно выбранных байт из заданной строки.

Параметры

string $string — Строка из которой выбираются случайные байты.

Примечание: Строка может содержать повторяющиеся байты. Когда байты дублируются, вероятность выбора значения равна его пропорции в строке. Если каждый байт встречается только один раз, байты будут выбраны равномерно.

int $length — Длина выходной строки

Возвращаемое значение

Случайная строка с длинной параметра $length, содержащая байты только из параметра $string.

Примеры

Случайное имя домена:

<?php
$randomizer = new \Random\Randomizer();

var_dump(sprintf(
"%s.example.com",
$randomizer->getBytesFromString('abcdefghijklmnopqrstuvwxyz0123456789', 16)
)); // string(28) "xfhnr0z6ok5fdlbz.example.com"

Числовой резервный код для многофакторной аутентификации:

<?php
$randomizer = new \Random\Randomizer();

var_dump(
implode('-', str_split($randomizer->getBytesFromString('0123456789', 20), 5))
); // string(23) "09898-46592-79230-33336"

Десятичное число произвольной точности:

<?php
$randomizer = new \Random\Randomizer();

// Обратите внимание, что в конце могут возвращаться
// нули, но все возможные десятичные дроби различны.
var_dump(sprintf(
'0.%s',
$randomizer->getBytesFromString('0123456789', 30)
)); // string(30) "0.217312509790167227890877670844"

Случайная строка в который каждый символ может быть a с 75% шансом и b с 25% шансом:

<?php
$randomizer = new \Random\Randomizer();

var_dump(
$randomizer->getBytesFromString('aaab', 16)
); // string(16) "baabaaaaaaababaa"

Случайная последовательность ДНК:

<?php
$randomizer = new \Random\Randomizer();

var_dump(
$randomizer->getBytesFromString('ACGT', 30)
); // string(30) "CGTAGATCGTTCTGATAGAAGCTAACGGTT"

getFloat()

Метод возвращает число с плавающей точкой между $min и $max. Открыты или закрыты границы интервала (т.е. являются ли $min и $max возможными результатами) зависит от значения параметра $boundary. По умолчанию используется полуоткрытый интервал [$mix, $max), т.е включая нижнюю и исключая верхнюю границу. Возвращаемые значения равномерно выбираются и равномерно распределяются в пределах настроенного интервала.

Равномерное распределение означает, что каждый возможный подынтервал содержит такое же количество возможных значений, как и каждый другой подынтервал одинакового размера. Например, при вызове ->getFloat(0, 1, IntervalBoundary::ClosedOpen) возвращаемое значение менее 0.5 равновероятно, как и возвращаемое значение не менее 0.5. Возвращаемое значение меньше 0.1, произойдёт в 10% случаев, как и возвращаемое значение не менее 0.9.

Используемый алгоритм представляет собой алгоритм γ-раздела, опубликованный в Drawing Random Floating-Point Numbers from an Interval. Frédéric Goualard, ACM Trans. Model. Comput. Simul., 32:3, 2022.

Параметры

float $min — Нижняя граница интервала возможных возвращаемых значений.

float $max — Верхняя граница интервала возможных возвращаемых значений.

\Random\IntervalBoundary $boundary = \Random\IntervalBoundary::ClosedOpen

Возвращаемые значения

Случайное число с плавающей точкой, такое что:

Примеры

Генерация случайной широты и долготы:

<?php
$randomizer = new \Random\Randomizer();

// Обратите внимание, что степень широты в два раза превышает
// степень долготы
//
// Для широты значение может быть как -90, так и 90.
// Для долготы значение может быть 180, но не -180, потому что 180
// и -180 относятся к одной и той же долготе.
var_dump(sprintf(
"Lat: %+.6f Lng: %+.6f",
$randomizer->getFloat(-90, 90, \Random\IntervalBoundary::ClosedClosed),
$randomizer->getFloat(-180, 180, \Random\IntervalBoundary::OpenClosed),
)); // string(32) "Lat: -51.742529 Lng: +135.396328"

nextFloat()

Этот метод эквивалентен ->getFloat(0, 1, \Random\IntervalBoundary::ClosedOpen). Внутренняя реализация проще и быстрее, и не требует явных параметров для получения общего случая интервала [0, 1).

Возвращаемое значение

Случайное число с плавающей точкой, такое что $float >= 0 && $float < 1.

Примеры

Симуляция подбрасывания монеты:

<?php
$randomizer = new \Random\Randomizer();

var_dump(
$randomizer->nextFloat() < 0.5
); // bool(true)

Получить true с 10% шансом

<?php
$randomizer = new \Random\Randomizer();

var_dump(
$randomizer->nextFloat() < 0.1
); // bool(false)

Предлагаемые версии PHP

Следующая 8.x

Изменения ломающие обратную совместимость

Имя класса \Random\IntervalBoundary больше недоступно. Пространство имён \Random зарезервировано для расширения случайных чисел и поиска кода на GitHub symbol:IntervalBoundary language:php — не выдаёт ни каких результатов. Таким образом, это теоретический вопрос.

Незатронутая функциональность PHP

Единственная затронутая функциональность — это класс Randomizer, который получает новые методы. Эти методы могут быть видны с помощью Reflection. Всё остальное не влияет.

Список изменений

Дополнительные материалы

Предыдущая Статья

Laravel: Рекомендации на 2022 год. Полное руководство

Следующая Статья

Laravel 10: Дата выхода и новые возможности