【问题标题】:PHP generator yield the first value, then iterate over the restPHP 生成器产生第一个值,然后迭代其余的
【发布时间】:2016-01-30 06:44:08
【问题描述】:

我有这个代码:

<?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() 行,就会发生致命错误:

致命错误:未捕获的异常“异常”,消息“无法倒带已运行的生成器”

我该如何解决这个问题?

【问题讨论】:

    标签: php generator yield


    【解决方案1】:

    问题在于foreachtry to reset(倒带)生成器。但是rewind() 会在生成器当前在第一个 yield 之后抛出异常。

    所以你应该避免使用foreach,而是使用while

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

    【讨论】:

      【解决方案2】:

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

      提案 0:remaining() 装饰器。

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

      PHP 7+:

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

      PHP 5.5+

      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;
      }
      

      可能会有一些间接开销。但从语义上讲,我认为这是相当优雅的。

      建议一:for() 代替 while()。

      作为一个不错的语法替代方案,我建议使用for() 而不是while() 来减少-&gt;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-&gt;next() 放入 for() 语句中,但我认为这不会增加可读性。


      我在本地(使用 PHP 5.6)做的一个小基准测试表明,这个带有 for() 或 while() 的版本显式调用 ->next()、current() 等比带有 foreach(generator() as $value) 的隐式版本慢.

      方案2:generator()函数中的offset参数

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

      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 行。

      【讨论】:

        猜你喜欢
        • 2023-03-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-05-16
        • 2019-06-05
        • 1970-01-01
        • 2016-06-07
        相关资源
        最近更新 更多