RecursiveIteratorIterator如何在PHP中工作?

var*_*uog 83 php spl iterator

RecursiveIteratorIterator工作怎么样?

PHP手册没有任何记录或解释.IteratorIterator和之间有什么区别RecursiveIteratorIterator

hak*_*kre 234

RecursiveIteratorIterator是一个具体的Iterator实现树遍历.它使程序员能够遍历实现RecursiveIterator接口的容器对象,请参阅Wikipedia中的Iterator,了解迭代器的一般原理,类型,语义和模式.

不同之处IteratorIterator在于具体的Iterator实现对象以线性顺序遍历(并且默认接受Traversable其构造函数中的任何类型),RecursiveIteratorIterator允许循环遍历对象的有序树中的所有节点,并且其构造函数采用a RecursiveIterator.

简而言之:RecursiveIteratorIterator允许您循环遍历树,IteratorIterator允许您循环遍历列表.我将在下面展示一些代码示例.

从技术上讲,这可以通过遍历所有节点的子节点(如果有的话)来消除线性.这是可能的,因为根据定义,节点的所有子节点都是a RecursiveIterator.然后,toplevel Iterator在内部RecursiveIterator通过它们的深度堆叠不同的s,并保持指向当前活动子的指针以Iterator进行遍历.

这允许访问树的所有节点.

基本原理与以下内容相同IteratorIterator:接口指定迭代的类型,基本迭代器类是这些语义的实现.与下面的示例相比,对于线性循环,foreach除非需要定义新的Iterator(例如某些具体类型本身未实现Traversable),否则通常不会考虑实现细节.

对于递归遍历 - 除非您不使用Traversal已经具有递归遍历迭代的预定义- 您通常需要实例化现有RecursiveIteratorIterator迭代,或者甚至编写一个递归遍历迭代,这是Traversable您自己进行这种类型的遍历迭代foreach.

提示:您可能没有实现自己的那个,所以这可能是您在实际经验中所做的差异.你会在答案的最后找到一个DIY建议.

技术差异简称:

  • 虽然IteratorIterator需要任何Traversable线性遍历,但RecursiveIteratorIterator需要更具体RecursiveIterator的循环遍历树.
  • 其中IteratorIterator公开其主要Iterator通过getInnerIerator(),RecursiveIteratorIterator提供当前活跃子Iterator只通过该方法.
  • 虽然IteratorIterator完全不了解父母或孩子之类的东西,但RecursiveIteratorIterator也知道如何获得和穿越孩子.
  • IteratorIterator不需要堆栈的迭代器,RecursiveIteratorIterator有这样的堆栈并且知道活动的子迭代器.
  • IteratorIterator由于线性而没有选择,其订单在哪里,RecursiveIteratorIterator可以选择进一步遍历并需要根据每个节点决定(通过模式RecursiveIteratorIterator确定).
  • RecursiveIteratorIterator有更多的方法比IteratorIterator.

总结一下:RecursiveIterator是一种在其自己的迭代器上工作的具体迭代类型(在树上循环),即RecursiveIterator.这与基本原理相同IteratorIerator,但迭代类型不同(线性顺序).

理想情况下,您也可以创建自己的套装.唯一需要的是你的迭代器Traversable通过Iterator或实现可能的IteratorAggregate.然后你可以使用它foreach.例如,某种三元树遍历递归迭代对象以及容器对象的相应迭代接口.


让我们回顾一些不那么抽象的现实例子.在接口,具体迭代器,容器对象和迭代语义之间,这可能不是一个坏主意.

以目录列表为例.考虑您在磁盘上有以下文件和目录树:

目录树

虽然具有线性顺序的迭代器只遍历顶层文件夹和文件(单个目录列表),但递归迭代器也会遍历子文件夹并列出所有文件夹和文件(包含其子目录列表的目录列表):

Non-Recursive        Recursive
=============        =========

   [tree]            [tree]
    ? dirA            ? dirA
    ? fileA           ? ? dirB
                      ? ? ? fileD
                      ? ? fileB
                      ? ? fileC
                      ? fileA
Run Code Online (Sandbox Code Playgroud)

您可以轻松地将其与IteratorIterator不遍历目录树的递归进行比较.并且RecursiveIteratorIterator可以像递归列表那样遍历到树中.

起初,有一个非常简单的例子DirectoryIterator,它实现Traversable,它允许foreach遍历了它:

$path = 'tree';
$dir  = new DirectoryIterator($path);

echo "[$path]\n";
foreach ($dir as $file) {
    echo " ? $file\n";
}
Run Code Online (Sandbox Code Playgroud)

上面的目录结构的示例输出是:

[tree]
 ? .
 ? ..
 ? dirA
 ? fileA
Run Code Online (Sandbox Code Playgroud)

如你所见,这还没有使用IteratorIteratorRecursiveIteratorIterator.相反,它只是使用foreach它在Traversable界面上运行.

由于foreach默认情况下只知道名为线性顺序的迭代类型,我们可能希望明确指定迭代类型.乍一看,它看起来似乎过于冗长,但出于演示目的(并且RecursiveIteratorIterator稍后会更加明显),请指定迭代的线性类型,明确指定IteratorIterator目录列表的迭代类型:

$files = new IteratorIterator($dir);

echo "[$path]\n";
foreach ($files as $file) {
    echo " ? $file\n";
}
Run Code Online (Sandbox Code Playgroud)

此示例与第一个示例几乎相同,不同之处在于$files现在是IteratorIterator一种迭代类型Traversable $dir:

$files = new IteratorIterator($dir);
Run Code Online (Sandbox Code Playgroud)

像往常一样,迭代行为由以下方式执行foreach:

foreach ($files as $file) {
Run Code Online (Sandbox Code Playgroud)

输出完全相同.那有什么不同呢?不同的是在其中使用的对象foreach.在第一个例子中,它是DirectoryIterator第二个例子中的a IteratorIterator.这显示了迭代器具有的灵活性:您可以相互替换它们,内部的代码foreach只是继续按预期工作.

让我们开始获取整个列表,包括子目录.

正如我们现在已经指定了迭代的类型,让我们考虑将其更改为另一种迭代类型.

我们知道我们现在需要遍历整个树,而不仅仅是第一层.要使用简单的工作,foreach我们需要一种不同类型的迭代器:RecursiveIteratorIterator.而且只能迭代具有RecursiveIterator接口的容器对象.

界面是合同.实现它的任何类都可以与RecursiveIteratorIterator.一起使用.这样一个类的一个例子是RecursiveDirectoryIterator,它类似于递归变体DirectoryIterator.

让我们在用I-word编写任何其他句子之前看到第一个代码示例:

$dir  = new RecursiveDirectoryIterator($path);

echo "[$path]\n";
foreach ($dir as $file) {
    echo " ? $file\n";
}
Run Code Online (Sandbox Code Playgroud)

第三个示例与第一个示例几乎相同,但它会创建一些不同的输出:

[tree]
 ? tree\.
 ? tree\..
 ? tree\dirA
 ? tree\fileA
Run Code Online (Sandbox Code Playgroud)

好吧,没有那么不同,文件名现在包含前面的路径名,但其余的看起来也相似.

如示例所示,即使目录对象已经嵌入了RecursiveIterator接口,这还不足以foreach遍历整个目录树.这就是实施的地方RecursiveIteratorIterator.示例4显示了如何:

$files = new RecursiveIteratorIterator($dir);

echo "[$path]\n";
foreach ($files as $file) {
    echo " ? $file\n";
}
Run Code Online (Sandbox Code Playgroud)

使用RecursiveIteratorIterator而不仅仅是前一个$dir对象将以foreach递归方式遍历所有文件和目录.然后列出所有文件,因为现在已经指定了对象迭代的类型:

[tree]
 ? tree\.
 ? tree\..
 ? tree\dirA\.
 ? tree\dirA\..
 ? tree\dirA\dirB\.
 ? tree\dirA\dirB\..
 ? tree\dirA\dirB\fileD
 ? tree\dirA\fileB
 ? tree\dirA\fileC
 ? tree\fileA
Run Code Online (Sandbox Code Playgroud)

这应该已经证明了平面和树遍历之间的区别.的RecursiveIteratorIterator是能够穿越任何树状结构,元素的列表.因为有更多信息(如迭代当前所处的级别),所以可以在迭代它时访问迭代器对象,例如缩进输出:

echo "[$path]\n";
foreach ($files as $file) {
    $indent = str_repeat('   ', $files->getDepth());
    echo $indent, " ? $file\n";
}
Run Code Online (Sandbox Code Playgroud)

例5的输出:

[tree]
 ? tree\.
 ? tree\..
    ? tree\dirA\.
    ? tree\dirA\..
       ? tree\dirA\dirB\.
       ? tree\dirA\dirB\..
       ? tree\dirA\dirB\fileD
    ? tree\dirA\fileB
    ? tree\dirA\fileC
 ? tree\fileA
Run Code Online (Sandbox Code Playgroud)

当然这不会赢得选美比赛,但它表明,使用递归迭代器可以获得更多信息,而不仅仅是的线性顺序.即使foreach只能表达这种线性,访问迭代器本身也可以获得更多信息.

与元信息类似,如何遍历树并因此对输出进行排序也有不同的方法.这是模式,RecursiveIteratorIterator可以使用构造函数进行设置.

下一个示例将告诉RecursiveDirectoryIterator删除点条目(...),因为我们不需要它们.但是,递归模式也将更改为SELF_FIRST在子项(子目录中的文件和子子目录)之前将父元素(子目录)first()带入:

$dir  = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
$files = new RecursiveIteratorIterator($dir, RecursiveIteratorIterator::SELF_FIRST);

echo "[$path]\n";
foreach ($files as $file) {
    $indent = str_repeat('   ', $files->getDepth());
    echo $indent, " ? $file\n";
}
Run Code Online (Sandbox Code Playgroud)

输出现在显示正确列出的子目录条目,如果您与之前的输出进行比较那些不存在:

[tree]
 ? tree\dirA
    ? tree\dirA\dirB
       ? tree\dirA\dirB\fileD
    ? tree\dirA\fileB
    ? tree\dirA\fileC
 ? tree\fileA
Run Code Online (Sandbox Code Playgroud)

因此,对于目录示例,递归模式控制返回树中的brach或leaf的内容和时间:

  • LEAVES_ONLY (默认):仅列出文件,没有目录.
  • SELF_FIRST (上图):列出目录,然后是那里的文件.
  • CHILD_FIRST (没有示例):首先列出子目录中的文件,然后列出目录.

示例5的输出与另外两种模式:

  LEAVES_ONLY                           CHILD_FIRST

  [tree]                                [tree]
         ? tree\dirA\dirB\fileD                ? tree\dirA\dirB\fileD
      ? tree\dirA\fileB                     ? tree\dirA\dirB
      ? tree\dirA\fileC                     ? tree\dirA\fileB
   ? tree\fileA                             ? tree\dirA\fileC
                                        ? tree\dirA
                                        ? tree\fileA
Run Code Online (Sandbox Code Playgroud)

当您将其与标准遍历进行比较时,所有这些都不可用.因此,当您需要绕过它时,递归迭代会稍微复杂一些,但它很容易使用,因为它的行为就像迭代器一样,您可以将它放入foreach并完成.

我认为这些是一个答案的足够例子.您可以在这个要点中找到完整的源代码以及显示漂亮的ascii-trees的示例:https://gist.github.com/3599532

自己动手:逐行完成RecursiveTreeIterator工作.

示例5演示了有关迭代器可用状态的元信息.但是,这是foreach迭代中有目的地证明的.在现实生活中,这自然属于内心RecursiveIterator.

一个更好的例子是RecursiveTreeIterator,它负责缩进,前缀等.请参阅以下代码片段:

$dir   = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
$lines = new RecursiveTreeIterator($dir);
$unicodeTreePrefix($lines);
echo "[$path]\n", implode("\n", iterator_to_array($lines));
Run Code Online (Sandbox Code Playgroud)

RecursiveTreeIterator的目的是逐行工作,输出很简单,有一个小问题:

[tree]
 ? tree\dirA
 ? ? tree\dirA\dirB
 ? ? ? tree\dirA\dirB\fileD
 ? ? tree\dirA\fileB
 ? ? tree\dirA\fileC
 ? tree\fileA
Run Code Online (Sandbox Code Playgroud)

与a结合使用时,RecursiveDirectoryIterator它会显示整个路径名,而不仅仅是文件名.其余的看起来不错.这是因为文件名是由...生成的SplFileInfo.这些应该显示为基本名称.所需的输出如下:

/// Solved ///

[tree]
 ? dirA
 ? ? dirB
 ? ? ? fileD
 ? ? fileB
 ? ? fileC
 ? fileA
Run Code Online (Sandbox Code Playgroud)

创建一个可以与RecursiveTreeIterator而不是使用的装饰器类RecursiveDirectoryIterator.它应该提供当前的基本名称SplFileInfo而不是路径名.最终的代码片段可能如下所示:

$lines = new RecursiveTreeIterator(
    new DiyRecursiveDecorator($dir)
);
$unicodeTreePrefix($lines);
echo "[$path]\n", implode("\n", iterator_to_array($lines));
Run Code Online (Sandbox Code Playgroud)

这些片段包括附录$unicodeTreePrefix中的要点的一部分:自己动手:逐行制作RecursiveTreeIterator工作..

  • 好吧,这不回答我的"为什么"的问题,你只是说了更多的话,没有多说.也许你开始实际上是一个重要的错误?指出它,不要把它保密. (2认同)

sal*_*the 30

是什么的差异IteratorIteratorRecursiveIteratorIterator

要理解这两个迭代器之间的区别,首先必须先了解一下使用的命名约定以及"递归"迭代器的含义.

递归和非递归迭代器

PHP具有非"递归"迭代器,例如ArrayIteratorFilesystemIterator.还有"递归"迭代器,如RecursiveArrayIteratorRecursiveDirectoryIterator.后者有方法可以将它们钻进去,前者则没有.

当这些迭代器的实例自行循环时,即使是递归的,即使循环遍历嵌套数组或带有子目录的目录,这些值也只来自"顶层".

递归迭代器实现递归行为(via hasChildren(),getChildren())但不利用它.

将递归迭代器视为"递归"迭代器可能更好,它们具有递归迭代的能力,但简单地迭代其中一个类的实例将不会这样做.要利用递归行为,请继续阅读.

RecursiveIteratorIterator

这是RecursiveIteratorIterator进入游戏的地方.它具有如何调用"递归"迭代器的知识,以便在正常的平坦循环中向下钻取结构.它将递归行为付诸行动.它主要完成跨越迭代器中每个值的工作,查看是否有"子"进入或不进入,以及进入和退出这些子集合.你将一个实例粘贴RecursiveIteratorIterator到一个foreach中,潜入结构中,这样你就不必这样做了.

如果RecursiveIteratorIterator没有使用,你必须编写自己的递归循环来利用递归行为,检查"递归"迭代器hasChildren()和使用getChildren().

这是一个简短的概述RecursiveIteratorIterator,它与它有什么不同IteratorIterator?好吧,你基本上都在问同样的问题:小猫和树之间有什么区别?只是因为两者都出现在同一个百科全书(或手册,对于迭代器)并不意味着你应该在两者之间混淆.

IteratorIterator

它的工作IteratorIterator是获取任何Traversable对象,并将其包装以使其满足Iterator接口.这样做的用途是能够在非迭代器对象上应用特定于迭代器的行为.

举一个实际的例子,这个DatePeriod课程Traversable不是一个Iterator.因此,我们可以循环其值foreach()但不能执行我们通常使用迭代器的其他事情,例如过滤.

任务:在接下来的四周的周一,周三和周五进行循环.

是的,这是由琐碎foreach-ing比DatePeriod和使用if()的环内; 但这不是这个例子的重点!

$period = new DatePeriod(new DateTime, new DateInterval('P1D'), 28);
$dates  = new CallbackFilterIterator($period, function ($date) {
    return in_array($date->format('l'), array('Monday', 'Wednesday', 'Friday'));
});
foreach ($dates as $date) { … }
Run Code Online (Sandbox Code Playgroud)

上面的代码段不起作用,因为它CallbackFilterIterator需要一个实现Iterator接口的类的实例,而DatePeriod不是.但是,因为Traversable我们可以通过使用轻松满足该要求IteratorIterator.

$period = new IteratorIterator(new DatePeriod(…));
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,这与迭代迭代器类和递归没有任何关系,其中存在IteratorIterator和之间的区别RecursiveIteratorIterator.

摘要

RecursiveIteraratorIterator用于迭代RecursiveIterator("递归"迭代器),利用可用的递归行为.

IteratorIterator用于将Iterator行为应用于非迭代器,Traversable对象.

  • +1表示"递归".这个名字让我长时间误入歧途,因为`RecursiveIterator`中的`Recursive`意味着行为,而更合适的名称就是描述能力的名称,比如`RecursibleIterator`. (6认同)