PHPでRecursiveIteratorIteratorはどのように機能しますか?


88

どのように機能しRecursiveIteratorIteratorますか?

PHPマニュアルには、文書化も説明もほとんどありません。違いは何であるIteratorIteratorとはRecursiveIteratorIterator


2
例がありphp.net/manual/en/recursiveiteratoriterator.construct.phpはと紹介でもありphp.net/manual/en/class.iteratoriterator.php -あなたは悩みを理解している正確に何を指摘してくださいすることができます。マニュアルを理解しやすくするために何を含める必要がありますか?
Gordon

1
どのようにRecursiveIteratorIterator機能するか尋ねた場合、すでにどのようにIteratorIterator機能するか理解しましたか?つまり、基本的には同じですが、2つが使用するインターフェースのみが異なります。そして、あなたはいくつかの例にもっと興味がありますか、それとも基礎となるCコード実装の違いを見たいですか?
hakre

@ゴードン私は単一のforeachループがツリー構造のすべての要素をどのようにトラバースできるか確信がありませんでした
varuog

@hakra私は現在、すべての組み込みインターフェースと、splインターフェースおよびイテレーター実装を調査しようとしています。
varuog 2012

@hakreどちらも実際には非常に異なっています。IteratorIteratorマップIteratorとにIteratorAggregate入れられますIterator。ここでREcusiveIteratorIteratorは、recusivly aをトラバースするために使用されますRecursiveIterator
アダム

回答:


249

RecursiveIteratorIterator具体的なIterator実装ツリー走査です。これにより、プログラマーはRecursiveIteratorインターフェースを実装するコンテナーオブジェクトをトラバースできます。イテレーターの一般的な原則、型、セマンティクス、およびパターンについては、ウィキペディアのイテレーターを参照してください。

線形順でのIteratorIterator具体的なIterator実装オブジェクトトラバーサルとは異なり(デフォルトTraversableではコンストラクターで任意の種類を受け入れます)、はオブジェクトの順序付けされたツリーRecursiveIteratorIterator内のすべてのノードをループし、コンストラクターはを受け取ります。RecursiveIterator

つまりRecursiveIteratorIterator、ツリーIteratorIteratorをループすることができ、リストをループすることができます。以下にいくつかのコード例を示します。

技術的には、これは、ノードのすべての子(存在する場合)をトラバースすることで線形性を破ることによって機能します。これは、定義によりノードのすべての子が再びであるために可能RecursiveIteratorです。次に、トップレベルIteratorはさまざまなRecursiveIteratorをその深さで内部的にスタックし、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を介して可能ですIteratorIteratorAggregate。その後、で使用できます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";
}

この例では、ほぼ最初のものと同じ、違いはある$filesIteratorIteratorのための反復のタイプTraversable $dir

$files = new IteratorIterator($dir);

いつものように、反復の行為はによって実行されますforeach

foreach ($files as $file) {

出力はまったく同じです。では何が違うのでしょうか?異なるのは、内で使用されるオブジェクトforeachです。最初の例ではDirectoryIterator、2番目の例ではIteratorIteratorです。これは、イテレータの柔軟性を示しています。それらを相互に置き換えることができ、内部のコードforeachは期待どおりに機能し続けます。

サブディレクトリを含むリスト全体を取得してみましょう。

これで反復のタイプを指定したので、それを別のタイプの反復に変更することを検討しましょう。

最初のレベルだけでなく、ツリー全体をトラバースする必要があることはわかっています。これをシンプルforeachで機能させるには、異なるタイプのイテレータが必要ですRecursiveIteratorIterator。そして、それはRecursiveIteratorインターフェースを持つコンテナオブジェクトに対してのみ繰り返すことができます

インターフェースは契約です。それを実装するクラスは、とともに使用できますRecursiveIteratorIterator。そのようなクラスの例はRecursiveDirectoryIterator、の再帰的なバリアントのようなものですDirectoryIterator

I-wordで他の文を書く前に、最初のコード例を見てみましょう:

$dir  = new RecursiveDirectoryIterator($path);

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

この3番目の例は、最初の例とほとんど同じですが、いくつかの異なる出力が作成されます。

[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、子(サブディレクトリ内のファイルとサブサブディレクトリ)の前に親要素(サブディレクトリ)を最初に()取るように変更されます。

$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

したがって、再帰モードは、ディレクトリの例として、ツリー内の分岐または葉が何をいつ返すかを制御します。

  • LEAVES_ONLY (デフォルト):ファイルのみをリストし、ディレクトリはリストしません。
  • SELF_FIRST (上):リストディレクトリとそこにあるファイル
  • CHILD_FIRST (例なし):最初にサブディレクトリ内のファイルをリストし、次にディレクトリをリストします。

他の2つのモードでの例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

これらは1つの答えの十分な例だと思います。完全なソースコードと見栄えの良いアスキーツリーを表示する例をこの要旨で見つけることができます:https : //gist.github.com/3599532

自分で行う:1RecursiveTreeIteratorずつ作業を行います。

例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代わりに使用できるデコレータクラスを作成しますRecursiveDirectoryIteratorSplFileInfoパス名ではなく、現在のベース名を提供する必要があります。最終的なコードフラグメントは次のようになります。

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

を含むこれらのフラグメント$unicodeTreePrefixは、付録:自分で行う:RecursiveTreeIteratorごとに作業を行うの要点の一部です


提示された質問には回答せず、事実上のエラーが含まれ、反復のカスタマイズに移ると、コアポイントが失われます。全体として、あなたがあまり知らないトピックについて報奨金を得ようとするか、あなたが知っているなら、提起された質問への答えに蒸留することができないように、それは貧弱な試みのように見えます。
salathe

2
さて、私の「なぜ」の質問には答えませんが、あなたは多くのことを言わずにもっと多くの言葉を言うだけです。多分あなたは実際に数えるエラーから始めますか?指さして、秘密にしないでください。
hakre 2012

最初の文は正しくありません:「RecursiveIteratorIteratorは…をサポートするIteratorIteratorです」、これは真実ではありません。
salathe

1
@salathe:あなたのフィードバックのためにあなたをタンクに入れます 回答を編集して対処しました。確かに最初の文は間違っていて不完全だった。RecursiveIteratorIteratorこれは他のタイプと共通であるため、具体的な実装の詳細はまだ省略しましたが、実際にどのように機能するかについて、いくつかの技術情報を提供しました。私が思うの例はよく違いを示しています。反復のタイプがある 2の主な違い。反復のタイプを購入するかどうかは少し異なりますが、IMHOは反復のタイプのセマンティクスで容易ではありません。
hakre 2012

1
最初の部分は多少改善されていますが、例に移り始めると、それでも実際には不正確に変わります。水平方向のルールで答えをトリミングすると、はるかに改善されます。
2012

31

違いは何ですかIteratorIteratorとはRecursiveIteratorIterator

これら2つのイテレーターの違いを理解するには、まず、使用される命名規則と、「再帰的」イテレーターの意味について少し理解する必要があります。

再帰的および非再帰的な反復子

PHPには、などの非「再帰的」イテレータがArrayIteratorありFilesystemIteratorます。RecursiveArrayIteratorおよびなどの「再帰的」イテレータもありますRecursiveDirectoryIterator。後者にはそれらをドリルダウンできる方法がありますが、前者にはありません。

これらのイテレータのインスタンスが単独でループする場合、再帰的なものであっても、ネストされた配列またはサブディレクトリを持つディレクトリをループしても、値は「トップ」レベルからのみ取得されます。

再帰イテレータは、実装(経由再帰的な行動をhasChildren()getChildren())ませんが悪用し、それを。

それは「recursible」イテレータとして再帰イテレータを考えた方が良いかもしれないが、彼らが持っている能力をを再帰的に反復することがなく、単に、これらのクラスの1つのインスタンスを反復処理すると、それを行うことはありません。再帰的な振る舞いを利用するには、読み続けてください。

RecursiveIteratorIterator

ここでRecursiveIteratorIteratorゲームが始まります。これは、通常のフラットなループで構造にドリルダウンするような方法で「再帰可能な」イテレータを呼び出す方法に関する知識を持っています。再帰的な動作を実行します。これは基本的に、イテレータの各値をステップオーバーし、再帰する「子」があるかどうかを調べ、それらの子のコレクションに出入りする作業を行います。のインスタンスをRecursiveIteratorIteratorforeach に挿入すると、構造ダイブするので、その必要はありません。

RecursiveIteratorIteratorが使用されなかった場合は、独自の再帰ループを記述して、再帰的な動作を利用し、「再帰可能な」イテレータhasChildren()と照合してを使用する必要がありgetChildren()ます。

それがの簡単な概要ですがRecursiveIteratorIterator、それとどう違うのIteratorIteratorですか?ええと、あなたは基本的に、子猫と木の違いは何ですかと同じような質問をしています。両方が同じ百科事典(または反復子の場合はマニュアル)に表示されるからといって、2つの間で混乱する必要があるわけではありません。

IteratorIterator

の役割は、IteratorIterator任意のTraversableオブジェクトを取得し、Iteratorインターフェイスを満たすようにラップすることです。これを使用すると、非イテレータオブジェクトにイテレータ固有の動作を適用できます。

実際の例を示すと、DatePeriodクラスはですTraversableが、ではありませんIterator。そのため、その値をループすることはできますが、foreach()フィルタリングなど、通常イテレータで行う他のことはできません。

タスク:次の4週間の月曜日、水曜日、金曜日をループします。

はい、これはによって自明である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) {  }

上記のスニペットはCallbackFilterIteratorIteratorインターフェイスを実装してDatePeriodいないクラスのインスタンスを予期しているため機能しません。ただし、Traversableなので、を使用することでその要件を簡単に満たすことができますIteratorIterator

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

ご覧のとおり、これはイテレータクラスの反復や再帰とはまったく関係なく、との違いがIteratorIteratorありRecursiveIteratorIteratorます。

概要

RecursiveIteraratorIteratorRecursiveIterator利用可能な再帰動作を利用して、(「再帰可能」イテレータ)を反復するためのものです。

IteratorIteratorIterator非イテレータ、Traversableオブジェクトに動作を適用するためのものです。


オブジェクトのIteratorIterator線形オーダートラバーサルの標準タイプだけではないTraversableですか?そのままでそのまま使えるものforeachは?そしてさらに、ではないRecursiveIterator 、常にTraversableそのためだけでなくIteratorIteratorだけでなく、RecursiveIteratorIterator常に「適用するためのIterator非イテレータは、Traversableのは、オブジェクトへの行動を」?(今度はforeach、反復子タイプのインターフェースを実装するコンテナーオブジェクトに反復子オブジェクトを介して反復のタイプを適用するため、これらは常に反復子コンテナーオブジェクトですTraversable
hakre

私の答えが述べるように、IteratorIteratorTraversableオブジェクトをでラップすることに関するすべてのクラスですIteratorもう何もない。あなたはより一般的な用語を適用しているようです。
salathe

一見有益な答え。1つの質問は、RecursiveIteratorIteratorもオブジェクトをラップして、オブジェクトがIterator動作にアクセスできるようにすることではないでしょうか。2つの違いは、RecursiveIteratorIteratorがドリルダウンできるのに対し、IteratorIteratorはできないことです。
Mike Purcell

@salathe、再帰イテレータ(RecursiveDirectoryIterator)がhasChildren()、getChildren()の動作を実装しない理由を知っていますか?
anru

7
+1は「再帰可能」と言います。RecursiveinはRecursiveIterator動作を意味するため、この名前は長い間私を混乱させましたが、より適切な名前は、などの機能を説明する名前RecursibleIteratorでした。
ヤギ

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

これは完全に誤解を招くものです。new IteratorIterator(new ArrayIterator($array))はと同じです。new ArrayIterator($array)つまり、外側IteratorIteratorは何もしていません。さらに、出力のフラット化は何の関係もありませんiterator_to_array。イテレータを配列に変換するだけです。平坦化は、RecursiveArrayIteratorその内部反復子を歩く方法のプロパティです。
Quolonelの質問2014

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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.