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工作。。
TA贡献1829条经验 获得超7个赞
是什么的差异
IteratorIterator
和RecursiveIteratorIterator
?
要理解这两个迭代器之间的区别,首先必须先了解一下使用的命名约定以及“递归”迭代器的含义。
递归和非递归迭代器
PHP具有非“递归”迭代器,例如ArrayIterator
和FilesystemIterator
。还有“递归”迭代器,如RecursiveArrayIterator
和RecursiveDirectoryIterator
。后者有方法可以将它们钻进去,前者则没有。
当这些迭代器的实例自行循环时,即使是递归的,即使循环遍历嵌套数组或带有子目录的目录,这些值也只来自“顶层”。
递归迭代器实现递归行为(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
对象。
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
- 3 回答
- 0 关注
- 1626 浏览
添加回答
举报