В чем разница между генератором и массивом?

Сегодня команда 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";
}

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

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

Ответ 1

Разница заключается в эффективности. Например, многие языки, кроме 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 результата, вы обрабатываете только первые три записи.

Для получения дополнительной информации я написал сообщение по этому конкретному вопросу.

Ответ 2

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

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

Ответ 3

Массив должен содержать каждое значение, которое вы зацикливаете, прежде чем начинать цикл; генератор создает каждое значение "на лету" по мере его запроса, поэтому намного меньше памяти;

Массив работает со значениями, которые он содержит, и должен быть предварительно заполнен этими значениями; генератор может создавать значения в соответствии со специальными критериями, которые должны использоваться непосредственно... например. последовательность fibonnaci или буквы из не-A-Z-алфавита (рассчитанные по числовому значению 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, ' ';
}

Ответ 4

Как и в Python:

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

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

- learnpython.org