在PHP中可以使用多个迭代器吗?

时间:2010-03-14 11:24:38

标签: php design-patterns iterator

请回答VolkerK的答案,他提供了另一种解决方案,但我不能将两个帖子标记为答案。 :(


美好的一天!

我知道C#允许使用yield的多个迭代器,如下所述: Is Multiple Iterators is possible in c#?

在PHP中有和Iterator接口。是否可以为类实现多个迭代场景?

更多细节(编辑):

例如,我有类TreeNode实现单树节点。整个树只能使用一个这个类来表示。我想提供迭代器来迭代当前节点的所有直接和间接子节点,例如使用BreadthFirst或DepthFirst顺序。

我可以将这个Iterators实现为单独的类,但这样做我需要树节点将它的子集合公开为public。

C#伪代码:

 public class TreeNode<T> 
  {
  ...
     public IEnumerable<T> DepthFirstEnumerator
     {
         get
        {
            // Some tree traversal using 'yield return'
        }
     }

     public IEnumerable<T> BreadthFirstEnumerator
     {
         get
         {
             // Some tree traversal using 'yield return'
         }
     }
 }

4 个答案:

答案 0 :(得分:5)

是的,你可以。

foreach(new IteratorOne($obj) as $foo) ....

foreach(new IteratorTwo($obj) as $bar) .....

实际上,只要您的类实现了Iterator,就可以对其应用任意IteratorIterator。这是一件好事,因为应用的元迭代器不需要知道有关该类的任何信息。

例如,考虑像这样的可迭代类

class JustList implements Iterator
{
    function __construct() { $this->items = func_get_args(); }
    function rewind()      { return reset($this->items); }
    function current()     { return current($this->items); }
    function key()         { return key($this->items); }
    function next()        { return next($this->items); }
    function valid()       { return key($this->items) !== null; }
}

让我们定义一些元迭代器

class OddIterator extends FilterIterator {
    function accept() { return parent::current() % 2;  }
}

class EvenIterator extends FilterIterator {
    function accept() { return parent::current() % 2 == 0;  }
}

现在将元迭代器应用于基类:

 $list = new JustList(1, 2, 3, 4, 5, 6, 7, 8, 9);

 foreach(new OddIterator($list) as $p) echo $p;  // prints 13579
 foreach(new EvenIterator($list) as $p) echo $p; // prints 2468

更新:php没有内部类,所以你在这里运气不好,至少没有求助于eval。您的迭代器需要是单独的类,它们知道基类结构。通过在基类中提供实例化幕后迭代器的方法,可以降低它的危害:

 class TreeDepthFirstIterator implements Iterator 
 {
      function __construct($someTree).....
 }


 class Tree
 {
       function depthFirst() { return new TreeDepthFirstIterator($this); }
        ....
 }


 foreach($myTree->depthFirst() as $node).....

另一个选择是使用lambdas而不是foreach。这更好,更灵活,但需要php5.3:

 class Tree
 {
        function depthFirst($func) {
              while($node = .....)
                $func($node);

 .....

 $myTree->depthFirst(function($node) {
     echo $node->name;
 });

答案 1 :(得分:1)

此代码向您展示如何在类中添加多个迭代器。

class TreeNode {

public function getOddIterator () {
  return new OddIterator($this->nodes);
}

public function getEvenIterator () {
  return new EvenIterator($this->nodes);
}

}

答案 2 :(得分:1)

为了您的目的,在您的类中使用“mode”标志可能就足够了,因此用户可以选择是先使用bread还是使用深度优先迭代器。

class Tree {
  const TREE_DEPTH_FIRST = 0;
  const TREE_BREADTH_FIRST = 0;

  protected $mode;
  protected $current;

  public function __construct($mode=Tree::TREE_DEPTH_FIRST) {
    $this->mode = $mode;
  }

  public function setMode($mode) {
    ...
  }

  public function next() {
    $this->current = advance($this->current, $this->mode);
  }  
  ....
}

(以及对你的初始问题的简短回答:没有php没有yield return的语法糖,并且它没有内部私有类,即你需要的迭代器,你要返回到处理“原始”对象必须暴露给外部世界。所以你最终可能会为像ArrayIterator这样的迭代器对象“准备”所有元素,这是你使用{避免的事情{1}})

答案 3 :(得分:0)

您可以拥有多个迭代器。迭代器中的关键思想是负责访问和遍历列表对象并将其放入迭代器对象中。因此,如果您希望多个迭代器具有相同的列表或不同的列表;没问题。

您可以在此处找到四个不同的PHP示例:

http://www.php5dp.com/category/design-patterns/iterator/

您也可以将它们与链接列表一起使用。

干杯, 比尔