Генератор PHP выдает первое значение, затем перебирает остальные

У меня есть этот код:

<?php

function generator() {
yield 'First value';
for ($i = 1; $i <= 3; $i++) {
yield $i;
}
}

$gen = generator();

$first = $gen->current();

echo $first . '<br/>';

//$gen->next();

foreach ($gen as $value) {
echo $value . '<br/>';
}

Это выводит:

First value
First value
1
2
3

Мне нужно «Первое значение», чтобы уступить только один раз. Если я раскомментирую строку $ gen-> next (), произошла фатальная ошибка:

Неустранимая ошибка: необработанное исключение «Исключение» с сообщением «Невозможно перемотать генератор, который уже был запущен»

Как я могу решить это?

3

Решение

Проблема в том, что foreach попробуй сбросить (перемотка) Генератор. Но rewind() генерирует исключение, если генератор в настоящее время находится после первого выхода.

Таким образом, вы должны избегать foreach и использовать while вместо

$gen = generator();

$first = $gen->current();

echo $first . '<br/>';
$gen->next();

while ($gen->valid()) {
echo $gen->current() . '<br/>';
$gen->next();
}
4

Другие решения

ответ чумкиу правильный. Некоторые дополнительные идеи.

Предложение 0: оставшийся () декоратор.

(Это последняя версия, которую я добавляю сюда, но, возможно, лучшая)

PHP 7+:

function remaining(\Generator $generator) {
yield from $generator;
}

PHP 5.5+ < 7:

function remaining(\Generator $generator) {
for (; $generator->valid(); $generator->next()) {
yield $generator->current();
}
}

Использование (все версии PHP):

function foo() {
for ($i = 0; $i < 5; ++$i) {
yield $i;
}
}

$gen = foo();
if (!$gen->valid()) {
// Not even the first item exists.
return;
}
$first = $gen->current();
$gen->next();

$values = [];
foreach (remaining($gen) as $value) {
$values[] = $value;
}

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

Предложение 1: for () вместо while ().

В качестве хорошей синтаксической альтернативы я предлагаю использовать for() вместо while() чтобы уменьшить беспорядок от ->next() вызов и инициализация.

Простая версия, без вашего начального значения:

for ($gen = generator(); $gen->valid(); $gen->next()) {
echo $gen->current();
}

С начальным значением:

$gen = generator();

if (!$gen->valid()) {
echo "Not even the first value exists.<br/>";
return;
}

$first = $gen->current();

echo $first . '<br/>';
$gen->next();

for (; $gen->valid(); $gen->next()) {
echo $gen->current() . '<br/>';
}

Вы могли бы поставить первый $gen->next() в for() утверждение, но я не думаю, что это добавит много читабельности.


Небольшой тест, который я сделал локально (с PHP 5.6), показал, что эта версия с for () или while () с явными вызовами -> next (), current () и т. Д. Медленнее, чем неявная версия с foreach(generator() as $value),

Предложение 2: параметр смещения в функции генератора ()

Это работает, только если у вас есть контроль над функцией генератора.

function generator($offset = 0) {
if ($offset <= 0) {
yield 'First value';
$offset = 1;
}
for ($i = $offset; $i <= 3; $i++) {
yield $i;
}
}

foreach (generator() as $firstValue) {
print "First: " . $firstValue . "\n";
break;
}

foreach (generator(1) as value) {
print $value . "\n";
}

Это будет означать, что любая инициализация будет выполняться дважды. Может быть, не желательно.

Кроме того, он позволяет звонить как генератор (9999) с очень высокими номерами пропуска. Например. кто-то может использовать это для обработки последовательности генератора в чанках. Но начинать с 0 каждый раз, а затем пропускать огромное количество элементов, кажется плохой идеей с точки зрения производительности. Например. если данные поступают из файла, и пропуск означает чтение + игнорирование первых 9999 строк файла.

1

По вопросам рекламы [email protected]