PHP 8.3: Дополнение Randomizer
строительных блоковв
\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
- ClosedOpen (по умолчанию):
$min
может быть возвращено, `$max$ — нет. - ClosedClosed: могут быть возвращены как
$min
, так и$max
. - OpenClosed:
$min
не может быть возвращён,$max
может. - OpenOpen: ни
$min
, ни$max
не могут быть возвращены.
Возвращаемые значения
Случайное число с плавающей точкой, такое что:
- ClosedOpen (по умолчанию):
$float >= $min && $float < $max
- ClosedClosed:
$float >= $min && $float <= $max
- OpenClosed:
$float > $min && $float <= $max
- OpenOpen:
$float > $min && $float < $max
Примеры
Генерация случайной широты и долготы:
<?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
. Всё остальное не влияет.
Список изменений
- 1.3. Переименован третий параметр
getFloat()
с$bounds
в$boundary
, чтобы он соответствовал именам перечисления. - 1.2. Переименован
GetFloatBounds
вIntervalBoundary
. - 1.1. Переименован
getBytesFromAlphabet
вgetBytesFromString
, добавлено перечислениеGetFloatBounds