PHP: Деревья
Теория: Аккумулятор
В некоторых ситуациях, во время обхода дерева, нужна дополнительная информация, которая зависит от расположения узла. Ее невозможно получить из описания самого узла, так как узел ее не содержит. Эту информацию нужно собирать прямо во время обхода.
К такой информации, например, относится полный путь до файла или глубина текущего узла. Конкретный узел не знает о том, где он находится. Расположение файла в файловой структуре определяется узлами, которые ведут к конкретному файлу.
В этом уроке мы познакомимся с понятием аккумулятор, специальным параметром, который собирает нужные данные во время обхода дерева. Его введение усложняет код, но без него подобные задачи выполнить невозможно.
Возьмем для примера такую задачу: найдем все пустые директории в нашей файловой системе. Сначала реализуем простую версию, затем усложним ее и внедрим аккумулятор. Пример файловой системы ниже:
В этой структуре три пустых директории: /logs, /etc/apache и /etc/consul/data. Код, решающий эту задачу, выглядит так:
Самое необычное в этой реализации — функция из нашей библиотеки array_flatten. Зачем она нужна? Если оставить только array_map, то результат может удивить:
Такое происходит из-за возврата массива на каждом уровне вложенности. На выходе получается массив массивов, напоминающий по структуре исходное файловое дерево. Чтобы этого не происходило, нужно выправлять код с помощью array_flatten.
Попробуем усложнить задачу. Найдем все пустые директории, но с максимальной глубиной поиска 2 уровня. То есть директории /logs и /etc/apache подходят под это условие, а вот /etc/consul/data — нет.
Для начала нужно понять откуда брать глубину. В деревьях глубина считается как количество ребер от корня до нужного узла. Визуально ее посчитать легко, а что насчет кода? Глубину конкретного узла можно представить как глубину предыдущего узла плюс единица.
Следующий шаг – добавить переменную, которая передается при каждом рекурсивном вызове (проваливающимся в директорию). Эта переменная, в случае нашей задачи, содержит внутри себя текущую глубину. То есть на каждом уровне (внутри каждой директории) к ней добавляется единица. Такую переменную называют аккумулятором, так как она аккумулирует, то есть накапливает данные.
Единственная проблема заключается в том, что у исходной функции findEmptyDirPaths ровно один параметр – узел. С его помощью невозможно передавать глубину всем вложенным директориям и файлам. Поэтому придется ввести внутреннюю функцию, которая сможет "пробрасывать" аккумулятор дальше по дереву:
Можно пойти еще дальше и позволить указывать максимальную глубину снаружи:
Но возникает вопрос, а как сделать так, чтобы по умолчанию просматривалось все дерево? Например, можно взять заведомо большое число и сделать его значением по умолчанию. Такой подход сработает, но это хак. Правильный способ сделать это – использовать в качестве значения по умолчанию бесконечность INF.
.png)
