PHP中是否可以有多个迭代器?

4

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


你好!

我知道C#允许使用yield进行多个迭代,就像这里描述的那样:Is Multiple Iterators is possible in c#?

在PHP中,有一个Iterator接口。是否可以为类实现多个迭代方案?

更多细节(编辑):

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

我可以将这些迭代器实现为单独的类,但是这样做需要树节点将其子项集合公开为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'
         }
     }
 }

好东西,跨开发语言连接知识是很不错的。 - Dan Rosenstark
1
你对被接受的答案的哪一部分感兴趣?是类“提供”不同迭代器的部分,还是同时使用同一对象的两个迭代器的部分?还是两者都?;-) - VolkerK
我对类提供不同迭代器的情况感兴趣。 - artvolk
您可能想解释一下 yield return 的作用,或者给出一个使用案例的示例。 - Gordon
4个回答

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).....

另一种选择是使用lambda表达式而不是foreach循环。这更加优雅和灵活,但需要PHP5.3以上版本:

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

 .....

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

谢谢您的解释,但如果我需要迭代更复杂的结构(例如树),我需要更改迭代算法,而不仅仅是过滤一些元素。请查看原始消息的编辑... - artvolk

1

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

class TreeNode {

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

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

}

1

针对您的需求,您的类中可能只需要一个“模式”标志即可,这样用户就可以选择是使用广度优先迭代器还是深度优先迭代器。

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一样“准备”所有元素为一个迭代器对象,这正是您通过使用yield避免的事情)

抱歉,似乎我无法将两个帖子标记为已接受的答案,所以我在我的留言中注明您的留言也是我正在寻找的内容。 - artvolk

0

您可以拥有多个迭代器。迭代器的关键思想是将访问和遍历的责任从列表对象中取出并放入迭代器对象中。因此,如果您想要使用相同列表或不同列表的多个迭代器,都没有问题。

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

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

你也可以将它们与链表一起使用。

祝好, 比尔


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接