PHP生成器产生第一个值,然后迭代其余值

时间:2015-10-29 22:37:45

标签: php generator yield

我有这段代码:

<?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-&gt; next()行,则会发生致命错误:

致命错误:未捕获的异常'异常',消息'无法回放已经运行的生成器'

我该如何解决这个问题?

2 个答案:

答案 0 :(得分:6)

问题在于foreach try to reset(倒带)发电机。但是如果生成器当前在第一次收益之后,rewind()会抛出异常。

因此,您应该避免使用foreach并使用while代替

$gen = generator();

$first = $gen->current();

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

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

答案 1 :(得分:3)

chumkiu的回答是正确的。一些额外的想法。

提案0:剩余()装饰者。

(这是我在这里添加的最新版本,但可能是最好的版本)

PHP 7 +:

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

PHP 5.5+&lt; 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(),显式调用 - &gt; next(),current()等比带{{的隐式版本慢1}}。

提案2:generator()函数中的偏移量参数

仅当您可以控制生成器功能时才有效。

foreach(generator() as $value)

这意味着任何初始化都会运行两次。也许不可取。

它还允许像generator(9999)这样的调用具有非常高的跳跃数。例如。有人可以使用它来处理块中的生成器序列。但是每次从0开始然后跳过大量的项目在性能方面看起来真是一个坏主意。例如。如果数据来自文件,则跳过意味着读取+忽略文件的前9999行。