В чем разница между генератором и массивом?
Сегодня команда PHP выпустила версию PHP 5.5.0, которая включает поддержку генераторов. Читая документацию, я заметил, что он делает то же, что и массив.
Пример генератора команды PHP:
// Only PHP 5.5
function gen_one_to_three() {
for ($i = 1; $i <= 3; $i++) {
// Note that $i is preserved between yields.
yield $i;
}
}
$generator = gen_one_to_three();
foreach ($generator as $value) {
echo "$value\n";
}
Результат:
1
2
3
Но я могу сделать то же самое, используя массивы. И я все еще могу поддерживать совместимость с более ранними версиями PHP.
Посмотрите:
// Compatible with 4.4.9!
function gen_one_to_three() {
$results = array();
for ($i = 1; $i <= 3; $i++) {
$results[] = $i;
}
return $results;
}
$generator = gen_one_to_three();
foreach ($generator as $value) {
echo "$value\n";
}
Таким образом, вопрос: какова цель существования этой новой функции? Я получил возможность воспроизвести все примеры документации, не используя новую функцию, заменив ее массивом.
Может ли кто-нибудь дать хорошее объяснение и, возможно, пример, который не обязательно невозможен со старыми версиями, но использование генераторов может помочь в разработке?
4 ответа
Разница заключается в эффективности. Например, многие языки, кроме PHP, включают два range
функции, range()
а также xrange()
, Это действительно хороший пример генераторов и зачем их использовать. Давайте построим наши собственные:
function range($start, $end) {
$array = array();
for ($i = $start; $i <= $end; $i++) {
$array[] = $i;
}
return $array;
}
Теперь это действительно прямо вперед. Однако для больших диапазонов требуется ОГРОМНОЕ количество памяти. Если бы мы попытались запустить его с $start = 0
а также $end = 100000000
у нас скорее всего не хватит памяти!
Но если бы мы использовали генератор:
function xrange($start, $end) {
for ($i = $start; $i <= $end; $i++) {
yield $i;
}
}
Теперь мы используем постоянную память, но все еще имеем "массив" (например, структуру), который мы можем перебирать (и использовать с другими итераторами) в том же пространстве.
Он не заменяет массив, но обеспечивает эффективный способ избежать необходимости в памяти...
Но это также обеспечивает экономию с точки зрения генерации предметов. Поскольку каждый результат генерируется по мере необходимости, вы можете отложить выполнение (выборку или вычисление) каждого элемента до тех пор, пока он вам не понадобится. Так, например, если вам нужно было извлечь элемент из базы данных и выполнить некоторую сложную обработку вокруг каждой строки, вы можете отложить это с помощью генератора, пока вам действительно не понадобится эта строка:
function fetchFromDb($result) {
while ($row = $result->fetchArray()) {
$record = doSomeComplexProcessing($row);
yield $record;
}
}
Поэтому, если вам нужны только первые 3 результата, вам нужно обработать только первые три записи.
Для получения дополнительной информации я написал пост в блоге на эту тему.
Генераторы позволяют лениво оценивать сложные операторы. Таким образом вы экономите память, так как вам не нужно выделять все сразу.
Помимо того, что оба являются повторяемыми, они не почти одинаковы. array
является структурой данных, а генератор - нет.
Массив должен содержать каждое значение, над которым вы зацикливаетесь перед началом зацикливания; генератор создает каждое значение "на лету", как оно запрашивается, поэтому намного меньше памяти;
Массив работает со значениями, которые он содержит, и должен быть предварительно заполнен этими значениями; генератор может создавать значения в соответствии со специальными критериями для непосредственного использования... например, последовательность Фибоначчи или буквы из алфавита не-AZ (вычисляются по числовому значению UTF-8), эффективно позволяя alphaRange('א','ת');
РЕДАКТИРОВАТЬ
function fibonacci($count) {
$prev = 0;
$current = 1;
for ($i = 0; $i < $count; ++$i) {
yield $prev;
$next = $prev + $current;
$prev = $current;
$current = $next;
}
}
foreach (fibonacci(48) as $i => $value) {
echo $i , ' -> ' , $value, PHP_EOL;
}
РЕДАКТИРОВАТЬ
Просто для удовольствия, вот генератор, который будет возвращать ивритский алфавит в виде символов UTF-8
function hebrewAlphabet() {
$utf8firstCharacter = 1488;
$utf8lastCharacter = 1514;
for ($character = $utf8firstCharacter; $character <= $utf8lastCharacter; ++$character) {
yield html_entity_decode('&#'.$character.';', ENT_NOQUOTES, 'UTF-8');
};
}
foreach(hebrewAlphabet() as $character) {
echo $character, ' ';
}
Как в Python:
Когда итерация набора элементов начинает использовать оператор for, запускается генератор. Как только код функции генератора достигает оператора yield, генератор возвращает свое выполнение обратно в цикл for, возвращая новое значение из набора. Функция генератора может генерировать столько значений (возможно, бесконечных), сколько она хочет, получая каждое из них в свою очередь.
... Генераторы выполняют операторы yield по одному, делая паузу между ними, чтобы вернуть выполнение к основному циклу for.