Як RecursiveIteratorIterator
працює?
У посібнику PHP немає нічого особливо документованого чи поясненого. У чому різниця між IteratorIterator
і RecursiveIteratorIterator
?
Як RecursiveIteratorIterator
працює?
У посібнику PHP немає нічого особливо документованого чи поясненого. У чому різниця між IteratorIterator
і RecursiveIteratorIterator
?
RecursiveIteratorIterator
працює, ви вже зрозуміли, як це IteratorIterator
працює? Я маю на увазі, що це в основному однаково, лише інтерфейс, який споживається двома, різний. І вас більше цікавлять деякі приклади чи ви хочете побачити різницю базової реалізації коду С?
IteratorIterator
карти Iterator
та IteratorAggregate
в Iterator
, де REcusiveIteratorIterator
використовується для проходження рекусивно aRecursiveIterator
Відповіді:
RecursiveIteratorIterator
це конкретна Iterator
реалізація обходу дерева . Це дозволяє програмісту пройти об’єм контейнера, що реалізує RecursiveIterator
інтерфейс, див. Ітератор у Вікіпедії щодо загальних принципів, типів, семантики та зразків ітераторів.
На відміну від того, IteratorIterator
який конкретний Iterator
варіант обходу об’єкта реалізується в лінійному порядку (і за замовчуванням приймає будь-який тип Traversable
у своєму конструкторі), RecursiveIteratorIterator
дозволяє циклічний перегляд усіх вузлів в упорядкованому дереві об’єктів, а його конструктор приймає a RecursiveIterator
.
Коротше кажучи: RecursiveIteratorIterator
дозволяє циклічно перебирати дерево, IteratorIterator
дозволяє циклічно перебирати список. Я показую це на деяких прикладах коду нижче.
Технічно це працює шляхом виходу з лінійності шляхом обходу всіх дочірніх вузлів (якщо такі є). Це можливо, оскільки за визначенням усі дочірні вузли знову є a 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
за замовчуванням тип ітерації відомий лише з іменем лінійний порядок, ми можемо захотіти вказати тип ітерації явно. На перший погляд це може здатися занадто багатослівним, але для демонстраційних цілей (і щоб зробити різницю 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
При використанні у поєднанні з 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
оскільки це спільно з іншими типами, але я дав деякі технічні відомості про те, як це насправді працює. Приклади, які, на мою думку, добре показують відмінності: тип ітерації є основною відмінністю між ними. Немає ідеї, якщо ви купуєте тип ітерації, ви монетируєте це трохи по-іншому, але IMHO непростий із семантичними типами ітерацій.
У чому різниця між
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
-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
об’єктів.
IteratorIterator
просто стандартний тип обходу лінійного порядку Traversable
об’єктів? Ті, які можна було б використовувати без нього просто так, foreach
як є? А ще далі, це не RecursiveIterator
завждиTraversable
і , отже , не тільки , IteratorIterator
а й RecursiveIteratorIterator
завжди «для застосування Iterator
поведінка не-ітератор, об'єкти Traversable» ? (Я б зараз сказав, що 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