为了账号安全,请及时绑定邮箱和手机立即绑定

RecursiveIteratorIterator如何在PHP中工作?

RecursiveIteratorIterator如何在PHP中工作?

PHP
慕后森 2019-08-15 15:42:00
RecursiveIteratorIterator如何在PHP中工作?RecursiveIteratorIterator工作怎么样?PHP手册没有任何记录或解释。IteratorIterator和之间有什么区别RecursiveIteratorIterator?
查看完整描述

3 回答

?
临摹微笑

TA贡献1982条经验 获得超2个赞

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

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


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


$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 $dir:


$files = new IteratorIterator($dir);

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


foreach ($files as $file) {

输出完全相同。那有什么不同呢?不同的是在其中使用的对象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";

}

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


[tree]

 ├ tree\.

 ├ tree\..

 ├ tree\dirA

 ├ tree\fileA

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


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


$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是能够穿越任何树状结构,元素的列表。因为有更多信息(如迭代当前所处的级别),所以可以在迭代它时访问迭代器对象,例如缩进输出:


echo "[$path]\n";

foreach ($files as $file) {

    $indent = str_repeat('   ', $files->getDepth());

    echo $indent, " ├ $file\n";

}

和例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

当然这不会赢得选美比赛,但它表明,使用递归迭代器可以获得更多信息,而不仅仅是键和值的线性顺序。即使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";

}

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


[tree]

 ├ tree\dirA

    ├ tree\dirA\dirB

       ├ tree\dirA\dirB\fileD

    ├ tree\dirA\fileB

    ├ tree\dirA\fileC

 ├ tree\fileA

因此,对于目录示例,递归模式控制返回树中的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

当您将其与标准遍历进行比较时,所有这些都不可用。因此,当您需要绕过它时,递归迭代会稍微复杂一些,但是它易于使用,因为它的行为就像迭代器一样,您可以将它放入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));

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


[tree]

 ├ tree\dirA

 │ ├ tree\dirA\dirB

 │ │ └ tree\dirA\dirB\fileD

 │ ├ tree\dirA\fileB

 │ └ tree\dirA\fileC

 └ tree\fileA

与a结合使用时,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中的要点的一部分:自己动手:逐行制作RecursiveTreeIterator工作。。


查看完整回答
反对 回复 2019-08-15
?
千巷猫影

TA贡献1829条经验 获得超7个赞

是什么的差异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) { … }

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

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

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

摘要

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

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


查看完整回答
反对 回复 2019-08-15
?
森栏

TA贡献1810条经验 获得超5个赞

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


查看完整回答
反对 回复 2019-08-15
  • 3 回答
  • 0 关注
  • 1626 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信