Генерация случайных чисел PHP. Что такое генераторы в PHP Php yield использование

Несмотря на то, что php-генераторы доступны с php 5.5.0, они все еще почти не используются. Более того, большинство разработчиков, которых я знаю, понимают, как работают генераторы , но не видят, когда они могут быть полезны в реальной жизни.

Да, генераторы определенно смотрятся хорошо, но знаете... Я не понимаю, где они могут быть полезными для меня, разве что для расчета последовательности Фибоначчи.

И они не ошибаются, ведь даже примеры в php -документации слишком упрощены. Они только объясняют, как эффективно реализовать range или итерировать по строкам файла .

Но даже с этих простых пример мы можем понять ключевые преимущества использования генераторов : они просто упрощают итераторы.

Генераторы позволяют вам написать код, который использует foreach для итерации множества данных без необходимости выделения памяти под массив.

Держа этот факт в памяти, я попытаюсь объяснить, почему генераторы насколько здорово помогли мне решить задачи над которыми я работал в компании.

Сначала немного контекста

Я работаю в TEA . В основном, мы разрабатываем экосистему для электронных книжек. Это покрывает весь путь от получения файлов нужного формата от издателей до размещения их на e-commerce сайте и предоставления конечному потребителю возможности читать онлайн (используя браузер, написанный @johanpoirier) или с электронной книги.

Для возможности продавать книги и отображения релевантной информации потребителям, нам нужно много метаданных продуктов (заголовок, формат, цена, издатели, автор(ы), ...).

Итерация по крупному множеству данных

Для первого примера использования, давайте предположим, что у меня есть большая коллекция книг и я хочу отфильтровать те, которые можно читать у веб-просмотрщике.

По традиции я должен был бы написать что-то вроде:

rulerz->satisfies($ebook, $rule)) { $filteredEbooks = $ebook; } } return $filteredEbooks; }

Проблему легко увидеть: чем больше книг, тем больше нужно памяти для $filteredEbooks.

Одно из решений - создать итератор, который бы итерировал $ebooks и возвращал подходящие. Но для этого нам нужно было бы создать новый класс, кроме того, итераторы реализируются немного утомительно... К счастью, с php 5.5.0 мы можем использовать генераторы !

rulerz->satisfies($ebook, $rule)) { yield $ebook; } } }

Да, рефакторинг метода getEbooksEligibleToWebReader для использования генератора очень прост: заменяем передачу значений в переменную $filteredEbooks конструкцией yield .

Предположив, что $ebooks не массив книг , а итератор, или генератор (даже лучше!), потребление памяти теперь будет константой, не важно, сколько книг нужно вернуть, и мы уверены, что книги будут искаться только когда реально понадобятся.

Бонус: RulerZ внутри использует генераторы , так что мы можем переписать метод и остаться с той же оптимизацией по выделению памяти.

rulerz->filter($ebooks, $rule); }

Агрегация нескольких источников данных

Теперь рассмотрим момент получения $ebooks. Я вам не сказал, но они по факту приходят с разных источников: реляционной БД и Elasticsearch.

Мы можем написать простой метод, агрегирующий эти два источники:

db->prepare("SELECT * FROM ebook_catalog"); $stmt->execute(); $stmt->setFetchMode(\PDO::FETCH_ASSOC); foreach ($stmt as $data) { $ebooks = $this->hydrateEbook($data); } // and from Elasticsearch (findAll uses ES scan/scroll) $cursor = $this->esClient->findAll(); foreach ($cursor as $data) { $ebooks = $this->hydrateEbook($data); } return $ebooks; }

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

Мы можем начать использовать генераторы и возвратить результат:

db->prepare("SELECT * FROM ebook_catalog"); $stmt->execute(); $stmt->setFetchMode(\PDO::FETCH_ASSOC); foreach ($stmt as $data) { yield $this->hydrateEbook($data); } // and from Elasticsearch (findAll uses ES scan/scroll) $cursor = $this->esClient->findAll(); foreach ($cursor as $data) { yield $this->hydrateEbook($data); } }

Так, конечно, лучше, но у нас все равно есть проблема: наш метод getBooks выполняет слишком много работы! Мы должны разделить две ответственности (считывание данных с БД и вызов Elasticsearch ) в два метода:

getEbooksFromDatabase(); yield from $this->getEbooksFromEs(); } private function getEbooksFromDatabase() { $stmt = $this->db->prepare("SELECT * FROM ebook_catalog"); $stmt->execute(); $stmt->setFetchMode(\PDO::FETCH_ASSOC); foreach ($stmt as $data) { yield $this->hydrateEbook($data); } } private function getEbooksFromEs() { // and from Elasticsearch (findAll uses ES scan/scroll) $cursor = $this->esClient->findAll(); foreach ($cursor as $data) { yield $this->hydrateEbook($data); } }

Вы могли заметить использование yield from оператора (доступен с php 7.0), который позволяет делегировать использование генераторов . Это идеально, к примеру, для агрегации нескольких источников данных, которые используют генераторы .

yield from оператор работает с любым Traversable объектом, так что массивы и итераторы также могут быть использованы с этим оператором.

Используя такую конструкцию, мы можем агрегировать несколько источников данных пару строками кода:

getEbooksFromCSV(); yield from $this->getEbooksFromDatabase(); }

Сложная ленивая (по требованию) гидрация записей БД

Другой вариант использования генераторов - реализация ленивой гидрации, которая может обрабатывать связи.

Мне нужно было импортировать сотни тысяч заказов с давней БД в нашу систему, каждый заказ содержал несколько пунктов.

Наличие заказов и "пунктов заказов" было предпосылкой к тому, что мы должны сделать. Я написал метод, который возвращает сгидрированые заказы и при этом не становится слишком медленным или прожорливым.

Идея слегка наивная: сджойнить заказы с пунктами, и сгруппировать заказы и пункты заказов в цикле.

Используя генераторы , мне удалось реализовать метод, который получает заказы с БД и присоединяет соответствующие пункты заказа. Все это потребляет стабильное количество памяти. Генератор избавил от надобности держать все заказы и их пункты: текущий заказ - это все, что мне нужно, чтобы сагрегировать все данные.

Имитация асинхронных задач

Последнее, но тем не менее важное: генераторы так же могут быть использованы для имитации асинхронных задач. Пока я писал эту заметку, я наткнулся на пост

При вождении автомобиля – скорость это далеко не все. Но в WEB все решает скорость. Чем быстрее ваше приложение, тем лучше пользовательский опыт. Хорошо, эта статья о генераторах в PHP, так почему же мы говорим о скорости? Как вы увидите вскоре, генераторы привносят большие изменения по части скорости и потреблении памяти приложением.

Что такое PHP генераторы?

Добавленные в PHP в версии 5.5 , генераторы представляют собой функции, обеспечивающие простой механизм для циклической обработки данных, без необходимости создавать массив данных в памяти. Все еще не понимаете о чем речь? Тогда давайте посмотрим на PHP генераторы в действии.

Создаем файл generator_test.php со следующим содержанием:


$array = ;
for($i = 0; $i < $max; $i++) {
$array = $i;
}
return $array;
}

Foreach (getRange(15) as $range) {
echo "Данные {$range}
";
}

Затем в папке где у нас лежит этот файл открываем консоль и пишем следующее

Http://localhost:8000/generator_test.php

Результат будет такой:

Данные 1
Данные 2
….
Данные 15

Код выше достаточно прост. Однако, давайте сделаем небольшое изменение в нем:


echo "Данные {$range}
";
}

Теперь диапазон генерируемых чисел находится в пределах от 0 до константы PHP_INT_MAX, которая представляет собой наибольшее целое число, которое способен представить интерпретатор PHP . После этого опять идем в браузер и обновляем страницу. Однако на этот раз, вместо обычного текста получаем сообщение о том, что превышен объем доступной памяти, вследствие чего работа скрипта была аварийно завершена.

Что за досада – у PHP закончилась память! Первое что приходит на ум – это редактировать настройку memory_limit в php.ini . Но давайте спросим себя – действительно ли это так эффективно? Неужели мы хотим, чтобы какой-то единственный скрипт занимал всю доступную память?

Используем генераторы

Давайте напишем ту же самую функцию, что и выше, вызовем ее с тем же значением PHP_INT_MAX и запустим снова. Но в этот раз мы создадим функцию-генератор .

Function getRange($max = 10) {
for($i = 1; $i < $max; $i++) {
yield $i;
}
}

Foreach (getRange(PHP_INT_MAX) as $range) {
echo "Данные {$range}
";
}

Определяя функцию getRange на этот раз, мы всего лишь проходим по значениям и генерируем вывод. Ключевое слово yield похоже на инструкцию return тем, что возвращает значение из функции, но единственное отличие заключается в том, что yield возвращает значение только тогда, когда это необходимо и не пытается вместить весь массив данных в память за один раз. Перейдя к браузеру, вы должны увидеть данные, отображаемые на странице. Обратите внимание на тот факт, что генераторы в PHP могут быть использованы только лишь из функции .

Зачем нужны генераторы?

Время от времени возникают такие задачи, когда нам необходимо обработать большие объемы данных (например, файлы логов), выполнить вычисления на больших выборках из базы и т.д. И мы отнюдь не хотим, чтобы эти операции занимали всю доступную память, так мы должны стараться сохранять память насколько это возможно. Данные не обязательно должны быть большими – PHP генераторы эффективны вне зависимости от размера данных. И не забывайте, что наша цель – сделать приложение быстрым и при этом таким, чтобы оно потребляло как можно меньше памяти.

Возврат ключей

Бывают случаи, когда нам необходимо возвращать не просто значение, а пару ключ-значение. При использовании генераторов, мы можем генерировать пары ключ-значение следующим образом.

Function getRange($max = 10) {
for($i = 0; $i < $max; $i++) {
$value = $i * mt_rand();
yield $i => $value;
}
}
?>

Использовать данную функцию мы можем также как и простой массив:

Foreach (getRange(PHP_INT_MAX) as $key => $value) {
echo "Ключ {$key} имеет значение {$value}";
}

Отсылка значений генераторам

Генераторы также могут принимать значения. Под этим подразумевается, что генераторы позволяют нам вставлять значения, которое может представлять собой подобие команды или еще что-то. Например, мы можем отправить значение в наш генератор, которое сигнализирует о необходимости остановки исполнения или изменения выходных данных. Далее пример кода:

Function getRange($max = 10) {
for($i = 1; $i < $max; $i++) {
$inject = yield $i;
if($inject === "stop") return;
}
}

$generator = getRange(PHP_INT_MAX);

Foreach($generator as $range) {
if($range === 10000) {
// посылаем сообщение генератору
$generator -> send("stop");
}
print "Значение {$range}
";
}

Отмечу, что использование инструкции return в функции-генераторе приведет к немедленному выходу из этой функции.

В заключении отмечу, что генераторы предлагают значительное улучшение производительности, которое мы не можем игнорировать. Большую часть времени нам не нужно иметь мощные сервера для выполнения нашего кода - нам просто необходимо сделать небольшой рефакторинг. И генераторы очень полезный инструмент, который мы должны использовать более часто, при этом, не злоупотребляя им.

Кстати, о генераторах я подробно рассказываю в моем курсе . Там есть и примеры и задания, которые помогут лучше усвоить материал.

Reg.ru: домены и хостинг

Крупнейший регистратор и хостинг-провайдер в России.

Более 2 миллионов доменных имен на обслуживании.

Продвижение, почта для домена, решения для бизнеса.

Более 700 тыс. клиентов по всему миру уже сделали свой выбор.

Фреймворк Bootstrap: быстрая адаптивная вёрстка

Пошаговый видеокурс по основам адаптивной верстки в фреймворке Bootstrap.

Научитесь верстать просто, быстро и качественно, используя мощный и практичный инструмент.

Верстайте на заказ и получайте деньги.

*Наведите курсор мыши для приостановки прокрутки.

Назад Вперед

Генерация случайных строк в PHP

В этой небольшой заметке я покажу, как можно сгенерировать случайную строку средствами PHP.

Подобная задача может возникнуть в самых разных ситуациях, например:

Создание случайного пароля из предустановленного набора символов;

Генерация случайного имени для файла или папки (в целях их скрытия и защиты);

Создание временного уникального идентификатора для какого-либо процесса;

Использование случайной строки в качестве "исходного материала" для более сложного скрипта и т.д.

При творческом подходе можно найти и другие сферы применения случайных строк.

В PHP нет специальной функции, которая выполняла бы интересующую нас задачу, поэтому такой инструмент нужно писать самому под свои нужды.

Ниже привожу один из вариантов решения этой задачи.

// Функция принимает 2 параметра: длину случайной строки и символы, которые участвуют в ее формировании function random_string ($str_length, $str_characters) { $str_characters = array (0,1,2,3,4,5,6,7,8,9,"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"); // Функция может генерировать случайную строку и с использованием кириллицы //$str_characters = array (0,1,2,3,4,5,6,7,8,9,"а","б","в","г","д","е","ж","з","и","к","л","м","н","о","п","р","с","т","у","ф","х","ц","ч","ш","щ","э","ю","я"); // Возвращаем ложь, если первый параметр равен нулю или не является целым числом if (!is_int($str_length) || $str_length < 0) { return false; } // Подсчитываем реальное количество символов, участвующих в формировании случайной строки и вычитаем 1 $characters_length = count($str_characters) - 1; // Объявляем переменную для хранения итогового результата $string = ""; // Формируем случайную строку в цикле for ($i = $str_length; $i > 0; $i--) { $string .= $str_characters; } // Возвращаем результат return $string; }

Поскольку материал рассчитан на новичков, сделаю пояснения по работе данной функции.

Сама функция генерации случайной строки принимает два параметра. Первый - количество символов, которое мы хотим видеть в результирующей строке. Второй - непосредственно массив символов, которые мы хотим использовать при генерации.

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

Если первый параметр корректный, то мы подсчитываем количество символов, участвующих в формировании строки с помощью функции count() и вычитаем из получившегося числа единицу. Для чего мы это делаем? На этот вопрос я отвечу чуть позже, а пока идем дальше по функции.

Объявив переменную $string для хранения итогового результата, мы приступаем к формированию случайной строки в цикле for .

В качестве начального значения счетчика $i цикла выступает количество символов, которое мы хотим увидеть в случайной строке ($str_length).

Затем идет условие, что цикл должен выполняться до тех пор, пока значение счетчика больше нуля.

Наконец, после каждой итерации цикла мы уменьшаем значение счетчика на единицу с помощью оператора декремента "--".

Таким образом, в цикле будет совершено столько итераций, сколько символов мы хотим получить в итоговой строке.

Ну а теперь посмотрим на тело цикла и вернемся к вопросу о том, для чего мы вычитали единицу из общего количества символов в переменной $str_characters .

Как вы видите, итоговую строку мы формируем, последовательно добавляя к переменной $string по одному символу за каждый проход по циклу.

Давайте посмотрим детальнее.

Мы используем функцию mt_rand() и передаем ей два параметра - ноль и ту самую уменьшенную на единицу переменную $characters_length . Функция mt_rand() позволяет нам просто сгенерировать случайное число в заданном диапазоне (т.е. от нуля до $characters_length ).

Смысл использования этой функции в том, что с ее помощью мы создаем случайное число, которое используется как индекс для массива $str_characters .

Индекс указывает на то, какой именно символ из массива $str_characters мы хотим получить.

Вот мы и подошли к ключевому моменту. Если бы первый элемент мы получали с помощью конструкции вида

$str_characters;

то тогда последний элемент можно было бы получить, написав следующее (ведь в массиве у нас 62 элемента):

$str_characters;

Это было бы вполне логично, но, к сожалению, неверно.

В действительности первый элемент доступен через

$str_characters;

а 62 элемент (последний) через

$str_characters;

Вы можете убедиться в этом просто выведя на экран данные значения.

Теперь становится совершенно понятно, для чего мы вычитали из 62 единицу. Только лишь для того, чтобы диапазон наших индексов был от 0 до 61. Именно это обеспечит нам корректное формирование случайной строки без вероятности случайно обратиться к несуществующему элементу с индексом 62.

Вот и все. Возвращаем значение с помощью оператора return - и наша функция готова.

Осталось вызвать ее и передать в качестве параметра желаемое количество символов в нашей случайной строке, например так:

Echo random_string(16,$str_characters);

В результате мы получим нечто совершенно бессвязное, вроде:

ATq4Lh9PNEm8cCxp

Цель достигнута, и теперь мы можем творчески применять нашу функцию там, где это необходимо.

Разумеется, наша функция универсальна и прекрасно работает также с русским языком. В коде выше вы можете увидеть еще один вариант массива $str_characters (он закомментирован), который содержит кириллические символы.

Для того, чтобы в этом убедиться, мы можем закомментировать первый массив $str_characters (с латинскими символами) и раскомментировать второй.

Теперь, если мы обновим страницу, то получим что-то вроде:

Жюю4д70ю779вхо5я

Таким образом, вы можете использовать любые символы, какие только захотите. Для этого вы можете или добавить их в уже существующий массив, либо создать новый.

И напоследок одно замечание, которое может быть полезно.

Уверен, что вы, как пользователь, встречались со случайно сгенерированными паролями, в которых едва ли можно понять, что что же это за символ:

То ли маленькая буква "l", то ли цифра "1";
- то ли русская "с", то ли английская "c";
- то ли русская "у", то ли английская "y";
- то ли ноль, то ли буква "о" (опять-таки, русская или английская);
- и т.д.

Поэтому, если вы сами генерируете какую-то строку для других пользователей, стоит позаботиться о том, чтобы она точно была им понятна. Представьте, что вы оказались на месте другого человека и гадаете: "Что же это такое?", "Буква это или цифра, черт возьми?"

Привет всем, сегодня я расскажу Вам о генерации случайных чисел в PHP . Благодаря лишь одной функции мы можем получить случайное число из заданного диапазона, что является довольно полезной задачей. Например, вы сможете вывести случайные записи на сайте, также можно реализовать капчу. Называется данная функция mt_rand , в качестве параметров Вы можете ничего не указывать, и тогда данная функция будет возвращать случайное число от нуля до максимально поддерживаемого значения. Наврятли Вам понадобится данная функция без использования параметров, но все же для того чтобы Вы знали о необязательной возможности их указывать я Вам это пояснил. Два параметра, которые можно задать в данной функции, это минимальное значение и максимальное значение, а Вам будет возвращено число из этого диапазона значений. Осталось лишь показать Вам пример ее использования.

echo mt_rand(). "
" ;

echo mt_rand(-5 , 7 ). "
" ;

echo mt_rand(3 , 20 ). "
" ;

?>

Теперь я предлагаю Вам посмотреть на результат выполнения нашего кода. Я не буду показывать картинку, т.к., при каждом обновлении страницы результат будет меняться. В первом случаем вы получите случайное число. Во втором в диапазоне от минус пяти до семи, и в третьем от трех до двадцати. Как видите, вы можете указывать и отрицательный диапазон, поэтому я привел Вам три примера. Больше о данной функции рассказать нечего, до скорого!

В этой статье мы будем изучать генераторы, которые обеспечивают простой способ реализовать простые итераторы без накладных расходов и сложности интерфейса Итератора.

Как работают генераторы?

Согласно Википедии, генератор "очень похожа на функцию, которая возвращает массив, в котором генератор имеет параметры, можно назвать и генерировать последовательность значений" . Генератор в основном похож на функцию, но вместо возвращения значения она дает столько значений, сколько это необходимо. Это подобно функции, но он действует как итератор.

Генераторы используют ключевое слово yield вместо return . Он действует аналогично для возврата, она возвращает значение вызвавшей функции, но вместо того, чтобы удалить функцию из стека, yield сохраняет его состояние. Эта функция позволяет продолжить с места, где она была, когда его вызывают снова. Фактически, нельзя возвращать значение генератора хотя вы можете использовать без возврата значения, чтобы прекратить его выполнение.

Руководство по PHP гласит: "Когда функция генератора вызывается, она возвращает объект, который может перемещаться." Это объект внутреннего класса генератора и реализует интерфейс итератора таким же образом. Перебирая этого объекта, PHP называет генератор каждый раз, когда ему требуется значение. Это состояние сохраняется только тогда, когда требуется следующее значение.

Вывод вышеуказонного кода будет:

Генератор начался

Приводится 0

Приводится 1

Приводится 2

Приводится 3

Приводится 4

Генератор закончился

Наш первый генератор

Генераторы не новая концепция, и уже существуют в таких языках, как C# , Python , JavaScript , и Ruby , а, как правило, идентифицируются по их использованию yield ключевого слова. Ниже приведен пример в Python :

Def file_lines(filename): file = open(filename) for line in file: yield line file.close() for line in file_lines("somefile"): .............

Давайте перезапишем генератор Python -а в PHP . (Отметим, что оба фрагмента не выполняют никакого вида ошибочной проверки.)

Функция генератора открывает файл а затем считает каждую строку файла, когда это требуется. Каждый раз, когда генератор вызывается, она продолжает от того места, откуда она была прервана. Она не начинается с самого начала, так как его состояние было сохранено, когда подтверждение действия выполнилось. После того как все линии были прочитаны, генератор просто отключается, и цикл заканчивается.

Возвращение ключей

PHP iterators состоят из пар ключевого/значения. В нашем примере, только значение было возвращаемым и поэтому ключи были числовыми (числовые ключи бывают по умолчанию). Если вы желаете вернуть ассоциативную пару, просто измените yield для включения ключа, используя синтаксис массива.

$line; ... } foreach (file_lines("somefile") as $key => $line) { ............. } ?>

Введение значения

yield не только возвращать значения, он также может получать значения из вне. Это делается путем вызова метода send() генератора объект со значением, если вы хотите передать и значение. Затем это значение может использоваться в вычислениях или делать другие вещи.

send("stop"); } echo "{$v}n"; } ?>

Вывод будет следующим:

Сохранение памяти с помощью генераторов

Генераторы удобны, расчета больших наборов, и если вы в то же время не хотите выделить память для всех результатов, или когда вы не знаете, вам нужны будут все результаты. Из-за способа обработки результатов, объем памяти может быть сведена к минимуму путем выделения памяти только для текущего результата.

Представьте себе функцию file() , которая возвращает все строки файла в виде массива. Управляя простой точкой отсчета(масштабом) для file() и наш демонстрационный пример функции file_lines() , каждый использующий те же самые случайные 100 текстовых файлов параграфа, используя Lipsum , показали, что функция file() использовала до 110 раз больше памяти, чем генератор.

С внедрением Генераторов, PHP поставил мощный инструмент в руках разработчиков. Теперь мы можем написать итераторы быстро, в процессе экономя много памяти. С помощью этого урока, я надеюсь, что вы получили достаточно, чтобы начать использовать их самостоятельно в ваших проектах.

Похожие публикации