Как работает RecursiveIteratorIterator в PHP?

как RecursiveIteratorIterator работы?

руководство PHP не имеет ничего документально или объяснено. В чем разница между IteratorIterator и RecursiveIteratorIterator?

4 ответов


RecursiveIteratorIterator конкретный Iterator реализация дерева. Это позволяет программисту пересекать объект контейнера, который реализует RecursiveIterator интерфейс, см. итератор в Википедии для общих принципов, типов, семантики и шаблонов итераторов.

в отличие от IteratorIterator который является конкретным Iterator реализация обхода объекта в линейном порядке (и по умолчанию принимает любой вид Traversable в своем конструкторе), то RecursiveIteratorIterator позволяет зацикливаться на всех узлах в упорядоченное дерево объектов и его конструктор принимает RecursiveIterator.

короче: RecursiveIteratorIterator позволяет сделать петлю над деревом,IteratorIterator позволяет перебирать список. Я покажу это с некоторыми примерами кода ниже.

технически это работает, нарушая линейность, пересекая все дочерние узлы (если любой.) Это возможно, потому что по определению все дочерние элементы узла снова являются RecursiveIterator. Начальный Iterator затем внутренне складывает разные RecursiveIterators по их глубине и сохраняет указатель на текущий активный sub Iterator для обхода.

это позволяет посетить все узлы дерева.

основные принципы те же, что и с IteratorIterator: интерфейс определяет тип итерации, и базовый класс итератора является реализацией этих семантика. Сравните с примерами ниже, для линейного цикла с foreach вы обычно не думаете о деталях реализации много, если вам не нужно определить новый Iterator (например, когда какой-то конкретный тип сам по себе не реализует Traversable).

для рекурсивного обхода - если вы не используете предварительно определенными Traversal что уже имеет рекурсивную итерацию обхода-вы обычно нужно создать экземпляр существующего RecursiveIteratorIterator итерация или даже написать рекурсивный обход итерации Traversable ваш собственный, чтобы иметь этот тип итерации обхода с foreach.

Совет: вы, вероятно, не реализовали ни тот, ни другой свой собственный, так что это может быть что-то стоит сделать для вашего практического опыта различий, которые они имеют. Вы найдете предложение DIY в конце ответа.

технические различия вкратце:

  • пока IteratorIterator принимает Traversable для линейного перемещения, RecursiveIteratorIterator нуждается в более конкретном RecursiveIterator петля над деревом.
  • здесь IteratorIterator выставляет свою главную Iterator via getInnerIerator(), RecursiveIteratorIterator предоставляет текущий активный суб -Iterator только с помощью этого метода.
  • пока IteratorIterator полностью не осознает ничего, как родитель или дети, RecursiveIteratorIterator знает, как получить и пересечь детей, а также.
  • IteratorIterator не нужен стек итераторов, RecursiveIteratorIterator имеет такой стек и знает активный суб-итератор.
  • здесь IteratorIterator имеет свой порядок из-за линейности и нет выбора, RecursiveIteratorIterator имеет выбор для дальнейшего обхода и должен решить для каждого узла (решено через режима в RecursiveIteratorIterator).
  • RecursiveIteratorIterator больше методов, чем IteratorIterator.

подведем итоги: RecursiveIterator - это конкретный тип итерации (цикл над деревом), который работает на собственных итераторах, а именно RecursiveIterator. То есть тот же основополагающий принцип, что и с IteratorIerator, но тип итерации отличается (линейный порядок).

в идеале вы также можете создать свой собственный набор. Единственное, что необходимо, это то, что ваш итератор реализует Traversable что возможно через Iterator или IteratorAggregate. Тогда вы можете использовать его с foreach. Например, какой-то объект рекурсивной итерации обхода троичного дерева вместе с соответствующим интерфейсом итерации для контейнера объект(ы).


давайте рассмотрим некоторые реальные примеры, которые не являются абстрактными. Между интерфейсами, конкретными итераторами, объектами контейнера и семантикой итераций это, возможно, не такая уж плохая идея.

возьмите список каталогов в качестве примера. Рассмотрим, что у вас есть следующий файл и дерево каталогов на диске:

Directory Tree

в то время как итератор с линейным порядком просто пересекает папка и файлы верхнего уровня (один список каталогов), рекурсивный итератор также проходит через подпапки и перечисляет все папки и файлы (список каталогов с перечислениями его подкаталогов):

Non-Recursive        Recursive
=============        =========

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

вы можете легко сравнить это с IteratorIterator который не делает рекурсии для обхода дерева каталогов. И RecursiveIteratorIterator который может переходить в дерево, как показывает рекурсивный список.

сначала очень простой пример с DirectoryIterator что реализует Traversable позволяет foreach to итерации за это:

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

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

примерный вывод для структуры каталогов выше, то:

[tree]
 ├ .
 ├ ..
 ├ dirA
 ├ fileA

как вы видите, это еще не используя IteratorIterator или RecursiveIteratorIterator. Вместо этого он просто использует foreach что работает на Traversable интерфейс.

As 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 во втором примере это IteratorIterator. Это показывает гибкость итераторов: вы можете заменить их друг на друга, код внутри foreach просто продолжать работать, как ожидалось.

позволяет начать получать весь список, включая подкаталоги.

как мы теперь указали тип итерации, давайте рассмотрим, чтобы изменить его на другой тип итерации.

мы знаем, что нам нужно пересечь все дерево сейчас, а не только первый уровень. Чтобы иметь эту работу с простым foreach нам нужен другой тип итератора: RecursiveIteratorIterator. И что можно только перебирать объекты-контейнеры, которые имеют the 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

хорошо, не так уж и отличается, имя файла теперь содержит путь впереди, но остальные выглядят похожими.

как показывает пример, даже объект каталога уже imlements RecursiveIterator интерфейс, этого еще недостаточно, чтобы сделать foreach пройдите по всему дереву каталогов. Вот где RecursiveIteratorIterator вступает в действие. Пример 4 показывает, как:

$files = new RecursiveIteratorIterator($dir);

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

используя the RecursiveIteratorIterator вместо сделает 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

это уже должно продемонстрировать разницу между плоскими и дерева. The 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

поэтому режим рекурсии управляет тем, что и когда возвращается 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-деревьев в этом gist:https://gist.github.com/3599532

Сделать Это Себя: сделать RecursiveTreeIterator работать построчно.

Пример 5 продемонстрировал, что существует мета-информация о состоянии итератора. Однако это было целенаправленно продемонстрировано внутри the 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 работать построчно..


в чем разница IteratorIterator и RecursiveIteratorIterator?

чтобы понять разницу между этими двумя итераторами, необходимо сначала немного понять используемые соглашения об именах и то, что мы подразумеваем под "рекурсивными" итераторами.

рекурсивные и нерекурсивные итераторы

PHP имеет не "рекурсивные" итераторы, такие как ArrayIterator и FilesystemIterator. Существуют также "рекурсивные" итераторы, такие как RecursiveArrayIterator и RecursiveDirectoryIterator. Этот у последних есть методы, позволяющие их просверлить, у первых-нет.

когда экземпляры этих итераторов зацикливаются самостоятельно, даже рекурсивные, значения поступают только с "верхнего" уровня, даже если зацикливаются на вложенном массиве или каталоге с подкаталогами.

рекурсивные итераторы реализовать рекурсивное поведение (через hasChildren(), getChildren()), но не использовать его.

возможно, было бы лучше думаю, рекурсивные итераторы как "recursible" итераторы, они имеют способности итерация рекурсивно, но просто итерация по экземпляру одного из этих классов этого не сделает. Использовать рекурсивное поведение, продолжайте читать.

RecursiveIteratorIterator

вот тут RecursiveIteratorIterator вступает в игру. Он имеет знание о том, как вызвать "рекурсивные" итераторы таким образом, чтобы детализировать структуру в нормальном режиме, плоская, петля. Это приводит рекурсивное поведение в действие. Он по существу выполняет работу по шагу над каждым из значений в итераторе, глядя, есть ли" дети " для рекурсии или нет, и шагая в и из этих коллекций детей. Вы вставляете экземпляр RecursiveIteratorIterator в foreach и это ныряет в структуру, так что вам не придется.

если RecursiveIteratorIterator не использовался, вам придется написать свои собственные рекурсивные циклы для использования рекурсивное поведение, проверять на "recursible" итератора 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 объекты.


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

при использовании 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));