Как работает RecursiveIteratorIterator в PHP?
как RecursiveIteratorIterator
работы?
руководство PHP не имеет ничего документально или объяснено. В чем разница между IteratorIterator
и RecursiveIteratorIterator
?
4 ответов
RecursiveIteratorIterator
конкретный Iterator
реализация дерева. Это позволяет программисту пересекать объект контейнера, который реализует RecursiveIterator
интерфейс, см. итератор в Википедии для общих принципов, типов, семантики и шаблонов итераторов.
в отличие от IteratorIterator
который является конкретным Iterator
реализация обхода объекта в линейном порядке (и по умолчанию принимает любой вид Traversable
в своем конструкторе), то RecursiveIteratorIterator
позволяет зацикливаться на всех узлах в упорядоченное дерево объектов и его конструктор принимает RecursiveIterator
.
короче: RecursiveIteratorIterator
позволяет сделать петлю над деревом,IteratorIterator
позволяет перебирать список. Я покажу это с некоторыми примерами кода ниже.
технически это работает, нарушая линейность, пересекая все дочерние узлы (если любой.) Это возможно, потому что по определению все дочерние элементы узла снова являются RecursiveIterator
. Начальный Iterator
затем внутренне складывает разные RecursiveIterator
s по их глубине и сохраняет указатель на текущий активный sub Iterator
для обхода.
это позволяет посетить все узлы дерева.
основные принципы те же, что и с IteratorIterator
: интерфейс определяет тип итерации, и базовый класс итератора является реализацией этих семантика. Сравните с примерами ниже, для линейного цикла с foreach
вы обычно не думаете о деталях реализации много, если вам не нужно определить новый Iterator
(например, когда какой-то конкретный тип сам по себе не реализует Traversable
).
для рекурсивного обхода - если вы не используете предварительно определенными Traversal
что уже имеет рекурсивную итерацию обхода-вы обычно нужно создать экземпляр существующего RecursiveIteratorIterator
итерация или даже написать рекурсивный обход итерации Traversable
ваш собственный, чтобы иметь этот тип итерации обхода с foreach
.
Совет: вы, вероятно, не реализовали ни тот, ни другой свой собственный, так что это может быть что-то стоит сделать для вашего практического опыта различий, которые они имеют. Вы найдете предложение DIY в конце ответа.
технические различия вкратце:
- пока
IteratorIterator
принимаетTraversable
для линейного перемещения,RecursiveIteratorIterator
нуждается в более конкретномRecursiveIterator
петля над деревом. - здесь
IteratorIterator
выставляет свою главнуюIterator
viagetInnerIerator()
,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
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));