Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

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

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

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

Технически это работает путем выхода из линейности путем перемещения всех дочерних узлов (если есть). Это возможно, потому что по определению все дочерние элементы node снова являются a 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 имеет выбор для дальнейшего обхода и должен решить для каждого node (решено через за 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 перебирать его:

$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";
}

Этот пример почти идентичен первому, разница в том, что $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

Когда вы сравниваете это со стандартным обходом, все эти вещи недоступны. Поэтому рекурсивная итерация немного сложнее, когда вам нужно обернуть вокруг себя голову, однако ее легко использовать, потому что она ведет себя точно так же, как итератор, вы помещаете ее в 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

При использовании в сочетании с 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, являются частью сущности в приложении: Do It Yourself: выполните RecursiveTreeIterator Рабочую линию по строке.

Ответ 2

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

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

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

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

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

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

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

RecursiveIteratorIterator

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

Если RecursiveIteratorIterator не использовался, вам придется написать свои собственные рекурсивные циклы, чтобы использовать рекурсивное поведение, проверяя "рекурсивный" итератор hasChildren() и используя getChildren().

Итак, краткий обзор RecursiveIteratorIterator, как он отличается от IteratorIterator? Ну, вы в основном задаете такой же вопрос, как в чем разница между котенком и деревом? Просто потому, что оба появляются в одной энциклопедии (или в руководстве, для итераторов), это не значит, что вы должны путаться между ними.

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.

Ответ 3

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

Ответ 4

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