PHP 生成器产生第一个值,然后迭代其余值
PHP generator yield the first value, then iterate over the rest
我有这个代码:
<?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
我只需要 'First value' 屈服一次。如果我取消注释 $gen->next() 行,则会发生致命错误:
致命错误:未捕获异常 'Exception' 消息 'Cannot rewind a generator that was already run'
我该如何解决这个问题?
问题是,如果生成器当前在第一个 yield 之后,foreach
try to reset (rewind) the Generator. But rewind()
会抛出异常。
所以你应该避免 foreach
并使用 while
代替
$gen = generator();
$first = $gen->current();
echo $first . '<br/>';
$gen->next();
while ($gen->valid()) {
echo $gen->current() . '<br/>';
$gen->next();
}
chumkiu 的回答是正确的。一些额外的想法。
提案 0:remaining() 装饰器。
(这是我在这里添加的最新版本,但可能是最好的)
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:generator() 函数中的偏移参数
这只有在您可以控制生成器函数时才有效。
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";
}
这意味着任何初始化都会 运行 两次。也许并不理想。
它还允许像 generator(9999) 这样具有非常高的跳过数的调用。例如。有人可以使用它来分块处理生成器序列。但每次都从 0 开始,然后跳过大量项目,这在性能方面似乎确实是个坏主意。例如。如果数据来自文件,跳过意味着读取+忽略文件的前 9999 行。
我有这个代码:
<?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
我只需要 'First value' 屈服一次。如果我取消注释 $gen->next() 行,则会发生致命错误:
致命错误:未捕获异常 'Exception' 消息 'Cannot rewind a generator that was already run'
我该如何解决这个问题?
问题是,如果生成器当前在第一个 yield 之后,foreach
try to reset (rewind) the Generator. But rewind()
会抛出异常。
所以你应该避免 foreach
并使用 while
代替
$gen = generator();
$first = $gen->current();
echo $first . '<br/>';
$gen->next();
while ($gen->valid()) {
echo $gen->current() . '<br/>';
$gen->next();
}
chumkiu 的回答是正确的。一些额外的想法。
提案 0:remaining() 装饰器。
(这是我在这里添加的最新版本,但可能是最好的)
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:generator() 函数中的偏移参数
这只有在您可以控制生成器函数时才有效。
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";
}
这意味着任何初始化都会 运行 两次。也许并不理想。
它还允许像 generator(9999) 这样具有非常高的跳过数的调用。例如。有人可以使用它来分块处理生成器序列。但每次都从 0 开始,然后跳过大量项目,这在性能方面似乎确实是个坏主意。例如。如果数据来自文件,跳过意味着读取+忽略文件的前 9999 行。