首页 文章

RecursiveIteratorIterator如何在PHP中工作?

提问于
浏览
78

RecursiveIteratorIterator 如何运作?

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

4 回答

  • -1

    IteratorIterator和RecursiveIteratorIterator有什么区别?

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

    递归和非递归迭代器

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

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

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

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

    RecursiveIteratorIterator

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

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

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

    IteratorIterator

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

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

    TASK :接下来四周的周一,周三和周五 .

    是的,通过遍历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 ("recursible"迭代器),利用可用的递归行为 .

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

  • 30

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

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

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

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

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

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

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

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

    技术差异简称:

    • 虽然 IteratorIterator 采用任何 Traversable 进行线性遍历,但 RecursiveIteratorIterator 需要更具体的 RecursiveIterator 来循环遍历树 .

    • 其中 IteratorIterator 通过 getInnerIerator() 公开其主 IteratorRecursiveIteratorIterator 仅通过该方法提供当前活动子 Iterator .

    • 虽然 IteratorIterator 完全不知道父母或孩子之类的东西, RecursiveIteratorIterator 也知道如何获取和穿越儿童 .

    • IteratorIterator 不需要堆栈的迭代器, RecursiveIteratorIterator 有这样的堆栈并且知道活动的子迭代器 .

    • IteratorIterator 由于线性而无法选择, RecursiveIteratorIterator 可以选择进一步遍历,需要根据每个节点决定(通过mode per RecursiveIteratorIterator决定) .

    • RecursiveIteratorIterator 有比 IteratorIterator 更多的方法 .

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

    理想情况下,您也可以创建自己的套装 . 唯一需要的是你的迭代器通过 IteratorIteratorAggregate 实现 Traversable . 然后你可以使用 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
    

    如您所见,尚未使用 IteratorIteratorRecursiveIteratorIterator . 相反,它只是使用 Traversable 接口上运行的 foreach .

    由于 foreach 默认只知道名为线性顺序的迭代类型,因此我们可能希望明确指定迭代类型 . 乍一看,这似乎太冗长了,但是出于演示的目的(并且有所作为)随着 RecursiveIteratorIterator 稍后可见),让我们指定迭代的线性类型,明确指定目录列表的 IteratorIterator 迭代类型:

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

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

    $files = new IteratorIterator($dir);
    

    像往常一样,迭代行为由 foreach 执行:

    foreach ($files as $file) {
    

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

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

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

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

    界面是 Contract . 任何实现它的类都可以与 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 只能表达这种线性,访问迭代器本身也可以获得更多信息 .

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

    下一个示例将告诉 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";
    }
    

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

    [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
    

    当与 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 工作线 .

  • 220

    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
    
  • 0

    当与 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));
    

相关问题