RecursiveIteratorIterator如何在PHP中工作?

时间:2012-08-22 16:07:58

标签: php iterator spl

RecursiveIteratorIterator如何运作?

PHP手册没有任何记录或解释。 IteratorIteratorRecursiveIteratorIterator之间有什么区别?

4 个答案:

答案 0 :(得分:225)

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

IteratorIterator不同,Traversable是一个具体的Iterator实现对象遍历的线性顺序(默认情况下在其构造函数中接受任何类型的mode per RecursiveIteratorIterator),RecursiveIteratorIterator允许循环遍历对象的有序树中的所有节点,其构造函数采用RecursiveIterator

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

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

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

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

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

  

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

技术差异简称:

  • 虽然foreach需要IteratorIterator进行线性遍历,Traversable需要更具体的RecursiveIteratorIterator来循环遍历树。
  • RecursiveIterator通过IteratorIterator公开其主Iterator的位置,getInnerIerator()仅通过该方法提供当前有效的子RecursiveIteratorIterator
  • 虽然Iterator完全不了解父母或孩子之类的内容,但IteratorIterator知道如何获取和遍历儿童。
  • RecursiveIteratorIterator不需要堆栈的迭代器,IteratorIterator具有这样的堆栈并且知道活动的子迭代器。
  • RecursiveIteratorIterator由于线性而没有选择的顺序,IteratorIterator可以选择进一步遍历,需要根据每个节点决定(通过DirectoryIterator决定)。
  • RecursiveIteratorIterator的方法多于RecursiveIteratorIterator

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

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


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

以目录列表为例。假设您在磁盘上有以下文件和目录树:

  

Directory Tree

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

foreach

您可以轻松地将其与Non-Recursive Recursive ============= ========= [tree] [tree] ├ dirA ├ dirA └ fileA │ ├ dirB │ │ └ fileD │ ├ fileB │ └ fileC └ fileA 进行比较,后者不会遍历目录树。并且IteratorIterator可以遍历到树中,如递归列表所示。

首先是一个非常基本的示例,Traversable实现foreach,允许RecursiveIteratorIterator 迭代

RecursiveIteratorIterator

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

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

echo "[$path]\n";
foreach ($dir as $file) {
    echo " ├ $file\n";
}

如您所见,尚未使用[tree] ├ . ├ .. ├ dirA ├ fileA IteratorIterator。相反,它只是使用RecursiveIteratorIterator界面上运行的foreach

默认情况下,Traversable只知道名为线性顺序的迭代类型,我们可能希望明确指定迭代类型。乍一看,它看起来似乎太冗长了,但是为了演示目的(并且为了使foreach稍后更加明显),让我们指定迭代的线性类型,明确指定RecursiveIteratorIterator类型的迭代。目录列表:

IteratorIterator

此示例几乎与第一个相同,不同之处在于$files = new IteratorIterator($dir); echo "[$path]\n"; foreach ($files as $file) { echo " ├ $file\n"; } 现在是$files IteratorIterator类型的迭代Traversable {{1} }:

$dir

与往常一样,迭代行为由$files = new IteratorIterator($dir);

执行
foreach

输出完全相同。那有什么不同呢? foreach ($files as $file) { 中使用的对象不同。在第一个示例中,在第二个示例中它是foreach,它是DirectoryIterator。这显示了迭代器具有的灵活性:您可以相互替换它们,IteratorIterator中的代码可以继续按预期工作。

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

由于我们现在已经指定了迭代的类型,因此我们考虑将其更改为另一种迭代类型。

我们知道我们现在需要遍历整个树,而不仅仅是第一层。要使用简单的foreach进行工作,我们需要一种不同类型的迭代器:RecursiveIterator interface。而且只能迭代具有RecursiveDirectoryIterator

的容器对象

界面是合约。任何实现它的类都可以与foreach一起使用。这类的一个例子是Mode of the RecursiveIteratorIterator,它类似于RecursiveIteratorIterator的递归变体。

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

DirectoryIterator

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

$dir  = new RecursiveDirectoryIterator($path);

echo "[$path]\n";
foreach ($dir as $file) {
    echo " ├ $file\n";
}

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

如示例所示,即使目录对象已经嵌入[tree] ├ tree\. ├ tree\.. ├ tree\dirA ├ tree\fileA 接口,这还不足以使RecursiveIterator遍历整个目录树。这是foreach开始行动的地方。 示例4 显示了如何:

RecursiveIteratorIterator

使用$files = new RecursiveIteratorIterator($dir); echo "[$path]\n"; foreach ($files as $file) { echo " ├ $file\n"; } 而不是前一个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 能够遍历任何树状结构作为元素列表。因为有更多的信息(比如迭代当前所处的级别),所以可以在迭代它时访问迭代器对象,例如缩进输出:

RecursiveIteratorIterator

示例5的输出

echo "[$path]\n";
foreach ($files as $file) {
    $indent = str_repeat('   ', $files->getDepth());
    echo $indent, " ├ $file\n";
}

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

与元信息类似,如何遍历树并因此对输出进行排序也有不同的方法。这是https://gist.github.com/3599532,可以使用构造函数进行设置。

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

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

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

  • [tree] ├ tree\dirA ├ tree\dirA\dirB ├ tree\dirA\dirB\fileD ├ tree\dirA\fileB ├ tree\dirA\fileC ├ tree\fileA (默认值):仅列出文件,无目录。
  • LEAVES_ONLY(上图):列出目录,然后是那里的文件。
  • SELF_FIRST(没有例子):首先列出子目录中的文件,然后列出目录。

使用另外两种模式输出示例5

CHILD_FIRST

当您将其与标准遍历进行比较时,所有这些都无法使用。因此,当你需要绕过它时,递归迭代有点复杂,但它易于使用,因为它的行为就像迭代器一样,你将它放入 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 并完成。

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

  

自己动手:逐行制作foreach工作线。

示例5 表明存在关于迭代器状态的元信息。但是,这是有目的地在 RecursiveTreeIterator迭代中演示的。在现实生活中,这自然属于foreach

更好的例子是{{3}},它负责缩进,前缀等。请参阅以下代码片段:

RecursiveIterator

$dir = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS); $lines = new RecursiveTreeIterator($dir); $unicodeTreePrefix($lines); echo "[$path]\n", implode("\n", iterator_to_array($lines)); 旨在逐行工作,输出很简单,有一个小问题:

RecursiveTreeIterator

[tree] ├ tree\dirA │ ├ tree\dirA\dirB │ │ └ tree\dirA\dirB\fileD │ ├ tree\dirA\fileB │ └ tree\dirA\fileC └ tree\fileA 结合使用时,它会显示整个路径名,而不仅仅是文件名。其余的看起来不错。这是因为文件名由RecursiveDirectoryIterator生成。这些应该显示为基本名称。所需的输出如下:

SplFileInfo

创建一个可与/// Solved /// [tree] ├ dirA │ ├ dirB │ │ └ fileD │ ├ fileB │ └ fileC └ fileA 而不是RecursiveTreeIterator一起使用的装饰器类。它应该提供当前RecursiveDirectoryIterator的基本名称而不是路径名。最终的代码片段可能如下所示:

SplFileInfo

这些片段包括$lines = new RecursiveTreeIterator( new DiyRecursiveDecorator($dir) ); $unicodeTreePrefix($lines); echo "[$path]\n", implode("\n", iterator_to_array($lines)); 附录中的要点的一部分:自己动手:按行进行$unicodeTreePrefix工作。

答案 1 :(得分:30)

  

IteratorIteratorRecursiveIteratorIterator有什么区别?

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

递归和非递归迭代器

PHP具有非“递归”迭代器,例如ArrayIteratorFilesystemIterator。还有“递归”迭代器,例如RecursiveArrayIteratorRecursiveDirectoryIterator。后者有一些方法可以将它们钻进去,而前者则没有。

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

递归迭代器实现递归行为(通过hasChildren()getChildren())但不利用它。

将递归迭代器视为“递归”迭代器可能更好,它们可以递归迭代能力,但只是迭代这些类之一的实例就不会这样做。要利用递归行为,请继续阅读。

RecursiveIteratorIterator

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

如果未使用RecursiveIteratorIterator,则必须编写自己的递归循环以利用递归行为,检查“递归”迭代器的hasChildren()并使用getChildren()。< / p>

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

IteratorIterator

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

举一个实际的例子,DatePeriod类是Traversable,而不是Iterator。因此,我们可以使用foreach()循环其值,但不能执行我们通常使用迭代器进行的其他操作,例如过滤。

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

是的,foreach - 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) { … }

上述代码段不起作用,因为CallbackFilterIterator需要实现Iterator接口的类的实例,DatePeriod不会。但是,由于它是Traversable,因此我们可以使用IteratorIterator轻松满足该要求。

$period = new IteratorIterator(new DatePeriod(…));

正如你所看到的,迭代迭代器类和递归都没有没有,而IteratorIteratorRecursiveIteratorIterator之间存在差异。

摘要

RecursiveIteraratorIterator用于迭代RecursiveIterator(“递归”迭代器),利用可用的递归行为。

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

答案 2 :(得分:0)

RecursiveDirectoryIterator它显示整个路径名而不仅仅是文件名。其余的看起来不错。这是因为文件名是由SplFileInfo生成的。这些应该显示为基本名称。所需的输出如下:

$path =__DIR__;
$dir = new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS);
$files = new RecursiveIteratorIterator($dir,RecursiveIteratorIterator::SELF_FIRST);
while ($files->valid()) {
    $file = $files->current();
    $filename = $file->getFilename();
    $deep = $files->getDepth();
    $indent = str_repeat('│ ', $deep);
    $files->next();
    $valid = $files->valid();
    if ($valid and ($files->getDepth() - 1 == $deep or $files->getDepth() == $deep)) {
        echo $indent, "├ $filename\n";
    } else {
        echo $indent, "└ $filename\n";
    }
}

输出:

tree
 ├ dirA
 │ ├ dirB
 │ │ └ fileD
 │ ├ fileB
 │ └ fileC
 └ fileA

答案 3 :(得分:-1)

iterator_to_array()一起使用时,RecursiveIteratorIterator将递归遍历数组以查找所有值。这意味着它会使原始数组变平。

IteratorIterator将保留原始的层次结构。

此示例将清楚地显示差异:

$array = array(
               'ford',
               'model' => 'F150',
               'color' => 'blue', 
               'options' => array('radio' => 'satellite')
               );

$recursiveIterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($array));
var_dump(iterator_to_array($recursiveIterator, true));

$iterator = new IteratorIterator(new ArrayIterator($array));
var_dump(iterator_to_array($iterator,true));