Як 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внутрішньо складає різні RecursiveIterators за їх глибиною і зберігає вказівник на поточну активну підгрупу 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. Більше нічого . Здається, ви застосовуєте цей термін загальніше.
Recursivein 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