Как RecursiveIteratorIterator
работает?
В руководстве по PHP нет ничего особо документированного или объясненного. В чем разница между IteratorIterator
и RecursiveIteratorIterator
?
Как RecursiveIteratorIterator
работает?
В руководстве по PHP нет ничего особо документированного или объясненного. В чем разница между IteratorIterator
и RecursiveIteratorIterator
?
RecursiveIteratorIterator
работает, вы уже поняли, как IteratorIterator
работает? Я имею в виду, что это в основном одно и то же, только интерфейс, который они используют, отличается. И вас больше интересуют некоторые примеры или вы хотите увидеть разницу в базовой реализации кода C?
IteratorIterator
карты Iterator
и IteratorAggregate
в Iterator
, где REcusiveIteratorIterator
используется для обратного ходаRecursiveIterator
Ответы:
RecursiveIteratorIterator
- это конкретная Iterator
реализация обхода дерева . Он позволяет программисту перемещаться по объекту-контейнеру, реализующему RecursiveIterator
интерфейс. Общие принципы, типы, семантика и шаблоны итераторов см. В разделе « Итератор» в Википедии .
В отличие от IteratorIterator
конкретного Iterator
реализуемого обхода объекта в линейном порядке (и по умолчанию принимающего любой тип Traversable
в своем конструкторе), RecursiveIteratorIterator
позволяет выполнять цикл по всем узлам в упорядоченном дереве объектов, а его конструктор принимает RecursiveIterator
.
Вкратце: RecursiveIteratorIterator
позволяет перебирать дерево, IteratorIterator
позволяет перебирать список. Я покажу это на некоторых примерах кода ниже.
Технически это работает путем выхода из линейности путем обхода всех дочерних узлов (если таковые имеются). Это возможно, потому что по определению все дочерние элементы узла снова являются RecursiveIterator
. Затем верхний уровень Iterator
внутренне складывает различные RecursiveIterator
s по их глубине и сохраняет указатель на текущий активный подпрограмм Iterator
для обхода.
Это позволяет посещать все узлы дерева.
Основные принципы такие же, как и в случае IteratorIterator
: интерфейс определяет тип итерации, а базовый класс итератора является реализацией этой семантики. Сравните с примерами ниже, для линейного цикла foreach
вы обычно не задумываетесь о деталях реализации, если вам не нужно определять новый Iterator
(например, когда какой-то конкретный тип сам по себе не реализуется Traversable
).
Для рекурсивного обхода - если вы не используете предопределенный, Traversal
который уже имеет рекурсивную итерацию обхода - вам обычно нужно создать экземпляр существующей RecursiveIteratorIterator
итерации или даже написать рекурсивную итерацию обхода, которая является Traversable
вашей собственной, чтобы иметь этот тип итерации обхода foreach
.
Совет: вы, вероятно, не реализовали ни то, ни другое самостоятельно, так что это может быть чем-то стоящим для вашего практического опыта различий между ними. В конце ответа вы найдете предложение «Сделай сам».
Кратко о технических отличиях:
IteratorIterator
требуется любое Traversable
, RecursiveIteratorIterator
требуется более конкретное RecursiveIterator
для циклического обхода дерева.IteratorIterator
предоставляет свое основное переходное Iterator
отверстие getInnerIerator()
, RecursiveIteratorIterator
предоставляет текущий активный вспомогательный Iterator
только через этот метод.IteratorIterator
совершенно ничего не известно о родителях или детях, но также RecursiveIteratorIterator
знает, как получить и пересечь детей.IteratorIterator
не нуждается в стеке итераторов, RecursiveIteratorIterator
имеет такой стек и знает активный суб-итератор.IteratorIterator
имеет свой порядок из-за линейности и отсутствия выбора, RecursiveIteratorIterator
имеет выбор для дальнейшего обхода и должен решать для каждого узла (определяется с помощью режима perRecursiveIteratorIterator
).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
по умолчанию известен только тип итерации с именем linear order, мы могли бы явно указать тип итерации. На первый взгляд это может показаться слишком многословным, но для демонстрационных целей (и чтобы разница была 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
. И это можно делать только по объектам контейнера, у которых есть RecursiveIterator
интерфейс .
Интерфейс - это контракт. Любой класс, реализующий его, можно использовать вместе с RecursiveIteratorIterator
. Примером такого класса является RecursiveDirectoryIterator
, что-то вроде рекурсивного варианта DirectoryIterator
.
Давайте посмотрим на первый пример кода, прежде чем писать любое другое предложение с I-словом:
$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
) принимал дочерние элементы ( файлы и подкаталоги в подкаталоге):
$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
(без примера): сначала перечислите файлы в подкаталоге, затем в каталоге.Результат примера 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
Если сравнить это со стандартным обходом, все это недоступно. Таким образом, рекурсивная итерация немного сложнее, когда вам нужно обдумать ее, однако ее легко использовать, потому что она ведет себя так же, как итератор, вы помещаете ее в a foreach
и готово.
Думаю, этого достаточно для одного ответа. Вы можете найти полный исходный код, а также пример отображения красивых ascii-деревьев в этой сущности: 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
работу линия за строкой». .
RecursiveIteratorIterator
потому что это похоже на другие типы, но я дал некоторую техническую информацию о том, как это работает. Примеры, на мой взгляд, хорошо показывают различия: тип итерации является основным различием между ними. Понятия не имею, если вы покупаете тип итерации, вы делаете его немного по-другому, но ИМХО нелегко с семантикой типов итераций.
В чем разница
IteratorIterator
иRecursiveIteratorIterator
?
Чтобы понять разницу между этими двумя итераторами, нужно сначала немного понять используемые соглашения об именах и то, что мы подразумеваем под «рекурсивными» итераторами.
PHP имеет нерекурсивные итераторы, такие как ArrayIterator
и FilesystemIterator
. Существуют также «рекурсивные» итераторы, такие как RecursiveArrayIterator
и RecursiveDirectoryIterator
. У последних есть методы, позволяющие исследовать их, а у первых - нет.
Когда экземпляры этих итераторов зацикливаются сами по себе, даже рекурсивные, значения поступают только с «верхнего» уровня, даже если зацикливаются вложенный массив или каталог с подкаталогами.
Рекурсивные итераторы реализуют рекурсивное поведение (через hasChildren()
, getChildren()
), но не используют его.
Было бы лучше думать о рекурсивных итераторах как о «рекурсивных» итераторах, у них есть возможность рекурсивно повторяться, но простое повторение экземпляра одного из этих классов этого не сделает. Чтобы использовать рекурсивное поведение, продолжайте читать.
Вот RecursiveIteratorIterator
тут-то и пригодится. Он знает, как вызывать «рекурсивные» итераторы таким образом, чтобы детализировать структуру в обычном плоском цикле. Он приводит в действие рекурсивное поведение. По сути, он выполняет работу по обходу каждого из значений в итераторе, проверяя, есть ли «дочерние элементы», в которые нужно рекурсивно или нет, а также входя и выходя из этих наборов дочерних элементов. Вы вставляете экземпляр RecursiveIteratorIterator
в foreach, и он погружается в структуру, так что вам не нужно.
Если бы RecursiveIteratorIterator
не использовался, вам пришлось бы написать свои собственные рекурсивные циклы для использования рекурсивного поведения, проверяя «рекурсивный» итератор hasChildren()
и используя getChildren()
.
Итак, это краткий обзор того RecursiveIteratorIterator
, чем он отличается от IteratorIterator
? Ну, вы в основном задаете тот же вопрос, что и в чем разница между котенком и деревом? Тот факт, что оба они представлены в одной энциклопедии (или руководстве для итераторов), не означает, что вы должны запутаться между ними.
Задача IteratorIterator
- взять любой Traversable
объект и обернуть его так, чтобы он удовлетворял Iterator
интерфейсу. Использование для этого состоит в том, чтобы затем иметь возможность применить специфичное для итератора поведение к объекту, не являющемуся итератором.
Чтобы привести практический пример, DatePeriod
класс, Traversable
но не класс Iterator
. Таким образом, мы можем перебирать его значения, foreach()
но не можем делать другие вещи, которые обычно выполнялись бы с итератором, например фильтрацию.
ЗАДАЧА : Обсудите понедельник, среду и пятницу следующих четырех недель.
Да, это тривиально: foreach
перебрать 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
объектам, не являющимся итератором .
IteratorIterator
стандартный тип обхода линейного порядка для Traversable
объектов? Те, которые можно было бы использовать без него, foreach
как есть? И даже более того, не RecursiveIterator
всегда ли a Traversable
и, следовательно, не только, IteratorIterator
но и RecursiveIteratorIterator
всегда, «для применения Iterator
поведения к не-итератору, проходимым объектам» ? (Теперь я бы сказал, что foreach
применяет тип итерации через объект-итератор к объектам-контейнерам, которые реализуют интерфейс типа итератора, поэтому они всегда являются объектами-итераторами Traversable
)
IteratorIterator
это класс, предназначенный для упаковки Traversable
объектов в Iterator
. Больше ничего . Кажется, вы применяете этот термин в более общем смысле.
Recursive
in RecursiveIterator
подразумевает поведение, тогда как более подходящим именем было бы то, которое описывает возможности, например RecursibleIterator
.
При использовании 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
обхода внутреннего итератора.
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