【问题标题】:Multiple generators in a single loop within PHPPHP中的单个循环中的多个生成器
【发布时间】:2014-06-20 15:17:11
【问题描述】:

我需要编写一个简单的脚本来从多个文件中加载数据并以某种方式合并它。但是,鉴于文件可能非常庞大,我想部分加载数据。为此,我决定使用产量。根据示例,我发现我可以对单个生成器使用以下构造:

$generator = $someClass->load(); //load method uses yield so it returns generator object
foreach($generator as $i) {
  // do something
}

但是如果我想同时使用两个生成器呢?

$generatorA = $someClass1->load(); //load method uses yield so it returns generator object
$generatorB = $someClass2->load(); //load method uses yield so it returns generator object
foreach($generatorA as $i) {
  // how can I access to resultSet from generatorB here?
}

【问题讨论】:

    标签: php generator yield


    【解决方案1】:

    如果你想使用带有 AppendIterator 的生成器,你需要使用 NoRewindIterator:

    https://3v4l.org/pgiXB

    <?php
    function foo() {
            foreach ([] as $foo) {
                    yield $foo;
            }
    }
    $append = new AppendIterator();
    $append->append(new NoRewindIterator(foo()));
    
    var_dump(iterator_to_array($append));
    

    如果 Generator 从未真正调用过 yield,则尝试使用 AppendIterator 遍历裸 Generator 将导致致命错误:

    https://3v4l.org/B4Qnh

    <?php
    function foo() {
            foreach ([] as $foo) {
                    yield $foo;
            }
    }
    $append = new AppendIterator();
    $append->append(foo());
    
    var_dump(iterator_to_array($append));
    

    输出:

    Fatal error: Uncaught Exception: Cannot traverse an already closed generator in /in/B4Qnh:10
    Stack trace:
    #0 [internal function]: AppendIterator->rewind()
    #1 /in/B4Qnh(10): iterator_to_array(Object(AppendIterator))
    #2 {main}
      thrown in /in/B4Qnh on line 10
    
    Process exited with code 255.
    

    【讨论】:

      【解决方案2】:

      来自https://www.php.net/manual/en/language.generators.syntax.php#control-structures.yield.from

      生成器委托通过yield from

      在 PHP 7 中,生成器委托允许您从另一个生成值 生成器、Traversable 对象或 array 使用 yield from 关键字。 然后外部生成器将产生所有值 内部生成器、对象或数组,直到不再有效, 之后将在外部生成器中继续执行。

      因此可以使用yield from 组合两个(或更多)生成器。

      /**
        * Yield all values from $generator1, then all values from $generator2
        * Keys are preserved
        */
      function combine_sequentially(Generator $generator1, Generator $generator2): Generator
      {
          yield from $generator1;
          yield from $generator2;
      };
      

      或者更花哨的东西(这里不能使用yield from):

      /**
        * Yield a value from $generator1, then a value from $generator2, and so on
        * Keys are preserved
        */
      function combine_alternatively(Generator $generator1, Generator $generator2): Generator
      {
          while ($generator1->valid() || $generator2->valid()) {
              if ($generator1->valid()) {
                  yield $generator1->key() => $generator1->current();
                  $generator1->next();
              }
              if ($generator2->valid()) {
                  yield $generator2->key() => $generator2->current();
                  $generator2->next();
              }
          }
      };
      

      【讨论】:

        【解决方案3】:

        虽然AppendIterator 适用于Iterators,但它存在一些问题。

        首先,需要构造一个新对象而不仅仅是调用一个函数并不是很好。更糟糕的是,您需要更改 AppendIterator,因为您无法在其构造函数中提供内部迭代器。

        其次,AppendIterator 只接受Iterator 实例,所以如果你有一个Traversable,比如IteratorAggregate,那么你就不走运了。不是Iterator 的其他iterable 也是如此,例如array

        这个 PHP 7.1+ 函数结合了两个iterable:

        /**
         * array_merge clone for iterables using lazy evaluation
         *
         * As with array_merge, numeric elements with keys are assigned a fresh key,
         * starting with key 0. Unlike array_merge, elements with duplicate non-numeric
         * keys are kept in the Generator. Beware that when converting the Generator
         * to an array with a function such as iterator_to_array, these duplicates will
         * be dropped, resulting in identical behaviour as array_merge.
         *
         *
         * @param iterable ...$iterables
         * @return Generator
         */
        function iterable_merge( iterable ...$iterables ): Generator {
            $numericIndex = 0;
        
            foreach ( $iterables as $iterable ) {
                foreach ( $iterable as $key => $value ) {
                    yield is_int( $key ) ? $numericIndex++ : $key => $value;
                }
            }
        }
        

        使用示例:

        foreach ( iterable_merge( $iterator1, $iterator2, $someArray ) as $k => $v ) {}
        

        此函数是small library for working with iterable 的一部分,它也在其中进行了广泛的测试。

        【讨论】:

          【解决方案4】:

          Generators in PHP 实现Iterator interface,因此您可以合并/合并多个Generators,就像您可以合并多个Iterators。

          如果您想一个接一个地迭代两个生成器(合并 A + B),那么您可以使用AppendIterator

          $aAndB = new AppendIterator();
          $aAndB->append($generatorA);
          $aAndB->append($generatorB);
          
          foreach ($aAndB as $i) {
              ...
          

          如果你想同时迭代两个生成器,你可以使用MultipleIterator

          $both = new MultipleIterator();
          $both->attachIterator($generatorA);
          $both->attachIterator($generatorB);
          
          foreach ($both as list($valueA, $valueB)) {
              ...
          

          这两个的例子,包括。示例和注意事项也在我的这篇博文中:

          否则我不明白你的要求。如果你能澄清一下,我应该能给你更多的指示。

          【讨论】:

          • 我认为你用MultipleIterator 打到了头上也许是一个例子?
          • @DavidBarker:请参阅编辑,以及链接的博客文章以及更多示例。
          • 我实际上使用了你编辑的混音,但效果很好$both = new MultipleIterator(MultipleIterator::MIT_KEYS_ASSOC); $both-&gt;attachIterator($firstFileParser-&gt;loadData(), 'a'); $both-&gt;attachIterator($secondFileParser-&gt;loadData(), 'b');
          • @Moby04:刷新答案,我使用可用于 foreach 的 PHP 5.5 新功能使 MultipleIterator 示例不那么冗长。
          • 看起来不错。无论如何,我意识到在这种特定情况下由于某种原因我不能使用生成器,但有时它可能很有用,所以谢谢:)
          【解决方案5】:

          试试这个:

          <?php
          foreach($generatorA as $key=>$i) {
              $A=$i;//value from $generatorA
              $B=$generatorB[$key];//value from $generatorB
          }
          

          【讨论】:

            【解决方案6】:

            类似:

            $generatorA = $someClass1->load(); //load method uses yield so it returns generator object
            $generatorB = $someClass2->load(); //load method uses yield so it returns generator object
            
            $flag = true;
            $i = 0;
            while($flag === false) {
            
             if ($i >= count($generatorA) || $i >= count($generatorB)) {
                  $flag = true;
             }
            
             // Access both generators
             $genA = $generatorA[$i];
             $genB = $generatorB[$i];
            
            $i++;
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2019-07-07
              • 2019-07-25
              • 2011-10-25
              • 2016-06-30
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多