Інший, більш спрощений спосіб перетворення плоскої структури в $tree
ієрархічний. Для викриття потрібен лише один тимчасовий масив:
// add children to parents
$flat = array(); # temporary array
foreach ($tree as $name => $parent)
{
$flat[$name]['name'] = $name; # self
if (NULL === $parent)
{
# no parent, is root element, assign it to $tree
$tree = &$flat[$name];
}
else
{
# has parent, add self as child
$flat[$parent]['children'][] = &$flat[$name];
}
}
unset($flat);
Це все для введення ієрархії в багатовимірний масив:
Array
(
[children] => Array
(
[0] => Array
(
[children] => Array
(
[0] => Array
(
[name] => H
)
[1] => Array
(
[name] => F
)
)
[name] => G
)
[1] => Array
(
[name] => E
[children] => Array
(
[0] => Array
(
[name] => A
)
[1] => Array
(
[children] => Array
(
[0] => Array
(
[name] => B
)
)
[name] => C
)
)
)
)
[name] => D
)
Вихід є менш тривіальним, якщо ви хочете уникнути рекурсії (може бути тягарем з великими структурами).
Я завжди хотів вирішити "дилему" UL / LI щодо виведення масиву. Дилема полягає в тому, що кожен елемент не знає, чи слідкуватимуть діти чи ні, скільки попередніх елементів потрібно закрити. В іншій відповіді я вже вирішив це, використовуючи RecursiveIteratorIterator
і шукаючи getDepth()
та іншу метаінформацію, яку написав мій власний текст Iterator
: Введення вкладеної набірної моделі в<ul>
підпорядковані, але приховані, "закриті" . Ця відповідь також показує, що з ітераторами ви досить гнучкі.
Однак це був попередньо відсортований список, тому він не був би придатний для вашого прикладу. Крім того, я завжди хотів вирішити це для якоїсь стандартної структури дерева, HTML <ul>
та <li>
елементів.
Основна концепція, яку я придумав:
TreeNode
- Абстрагує кожен елемент на простий TreeNode
тип, який може надати його значення (наприклад Name
) та не має він дітей.
TreeNodesIterator
- A, RecursiveIterator
який здатний повторювати набір (масив) цих TreeNodes
. Це досить просто, оскільки TreeNode
тип уже знає, чи є у нього діти, а які -.
RecursiveListIterator
- RecursiveIteratorIterator
що має всі необхідні події, коли він рекурсивно перебирає будь-який вид RecursiveIterator
:
beginIteration
/ endIteration
- Початок і кінець основного списку.
beginElement
/ endElement
- Початок і кінець кожного елемента.
beginChildren
/ endChildren
- Початок і кінець кожного списку дітей. Це RecursiveListIterator
забезпечує лише ці події у формі викликів функцій. списки дітей, як це характерно для <ul><li>
списків, відкриваються та закриваються всередині його батьківського <li>
елемента. Тому endElement
подія закінчується після відповідної endChildren
події. Це можна змінити або налаштувати для розширення використання цього класу. Потім події розподіляються як виклики функцій до об'єкта декоратора, щоб не розірвати речі.
ListDecorator
- Клас "декоратор", який є лише приймачем подій RecursiveListIterator
.
Почніть з основної логіки виводу. Якщо взяти тепер ієрархічний $tree
масив, остаточний код виглядає наступним чином:
$root = new TreeNode($tree);
$it = new TreeNodesIterator(array($root));
$rit = new RecursiveListIterator($it);
$decor = new ListDecorator($rit);
$rit->addDecorator($decor);
foreach($rit as $item)
{
$inset = $decor->inset(1);
printf("%s%s\n", $inset, $item->getName());
}
Спочатку давайте розглянемо те, ListDecorator
що просто обгортає <ul>
і <li>
елементи, і вирішує, як виводиться структура списку:
class ListDecorator
{
private $iterator;
public function __construct(RecursiveListIterator $iterator)
{
$this->iterator = $iterator;
}
public function inset($add = 0)
{
return str_repeat(' ', $this->iterator->getDepth()*2+$add);
}
Конструктор бере ітератор списку, над яким працює. inset
- це лише допоміжна функція для гарного відступу виходу. Решта - лише вихідні функції для кожної події:
public function beginElement()
{
printf("%s<li>\n", $this->inset());
}
public function endElement()
{
printf("%s</li>\n", $this->inset());
}
public function beginChildren()
{
printf("%s<ul>\n", $this->inset(-1));
}
public function endChildren()
{
printf("%s</ul>\n", $this->inset(-1));
}
public function beginIteration()
{
printf("%s<ul>\n", $this->inset());
}
public function endIteration()
{
printf("%s</ul>\n", $this->inset());
}
}
Маючи на увазі ці функції виводу, це знову основне завершення / цикл виводу, я проходжу його покроково:
$root = new TreeNode($tree);
Створіть корінь, TreeNode
який використовуватиметься для запуску ітерації після:
$it = new TreeNodesIterator(array($root));
Це TreeNodesIterator
- RecursiveIterator
можливість рекурсивної ітерації над одним $root
вузлом. Він передається як масив, тому що цьому класу потрібно щось перебрати і дозволяє повторно використовувати набір дітей, що також є масивом TreeNode
елементів.
$rit = new RecursiveListIterator($it);
Це RecursiveListIterator
є , RecursiveIteratorIterator
що забезпечує зазначені події. Щоб скористатись цим, ListDecorator
потрібно лише надати (клас вище) та призначити addDecorator
:
$decor = new ListDecorator($rit);
$rit->addDecorator($decor);
Тоді все налаштовується лише foreach
над ним та виводить кожен вузол:
foreach($rit as $item)
{
$inset = $decor->inset(1);
printf("%s%s\n", $inset, $item->getName());
}
Як показано в цьому прикладі, вся логіка виводу інкапсульована в ListDecorator
класі і ця сингл foreach
. Весь рекурсивний проїзд був повністю інкапсульований у рекурсивні ітератори SPL, що забезпечували складену процедуру, що означає, що внутрішні виклики рекурсійних функцій не здійснюються.
На основі подій ListDecorator
можна спеціально змінювати вихідний сигнал та надавати кілька типів списків для однієї структури даних. Можна навіть змінити вхід, оскільки дані масиву були інкапсульовані в TreeNode
.
Повний код:
<?php
namespace My;
$tree = array('H' => 'G', 'F' => 'G', 'G' => 'D', 'E' => 'D', 'A' => 'E', 'B' => 'C', 'C' => 'E', 'D' => null);
// add children to parents
$flat = array(); # temporary array
foreach ($tree as $name => $parent)
{
$flat[$name]['name'] = $name; # self
if (NULL === $parent)
{
# no parent, is root element, assign it to $tree
$tree = &$flat[$name];
}
else
{
# has parent, add self as child
$flat[$parent]['children'][] = &$flat[$name];
}
}
unset($flat);
class TreeNode
{
protected $data;
public function __construct(array $element)
{
if (!isset($element['name']))
throw new InvalidArgumentException('Element has no name.');
if (isset($element['children']) && !is_array($element['children']))
throw new InvalidArgumentException('Element has invalid children.');
$this->data = $element;
}
public function getName()
{
return $this->data['name'];
}
public function hasChildren()
{
return isset($this->data['children']) && count($this->data['children']);
}
/**
* @return array of child TreeNode elements
*/
public function getChildren()
{
$children = $this->hasChildren() ? $this->data['children'] : array();
$class = get_called_class();
foreach($children as &$element)
{
$element = new $class($element);
}
unset($element);
return $children;
}
}
class TreeNodesIterator implements \RecursiveIterator
{
private $nodes;
public function __construct(array $nodes)
{
$this->nodes = new \ArrayIterator($nodes);
}
public function getInnerIterator()
{
return $this->nodes;
}
public function getChildren()
{
return new TreeNodesIterator($this->nodes->current()->getChildren());
}
public function hasChildren()
{
return $this->nodes->current()->hasChildren();
}
public function rewind()
{
$this->nodes->rewind();
}
public function valid()
{
return $this->nodes->valid();
}
public function current()
{
return $this->nodes->current();
}
public function key()
{
return $this->nodes->key();
}
public function next()
{
return $this->nodes->next();
}
}
class RecursiveListIterator extends \RecursiveIteratorIterator
{
private $elements;
/**
* @var ListDecorator
*/
private $decorator;
public function addDecorator(ListDecorator $decorator)
{
$this->decorator = $decorator;
}
public function __construct($iterator, $mode = \RecursiveIteratorIterator::SELF_FIRST, $flags = 0)
{
parent::__construct($iterator, $mode, $flags);
}
private function event($name)
{
// event debug code: printf("--- %'.-20s --- (Depth: %d, Element: %d)\n", $name, $this->getDepth(), @$this->elements[$this->getDepth()]);
$callback = array($this->decorator, $name);
is_callable($callback) && call_user_func($callback);
}
public function beginElement()
{
$this->event('beginElement');
}
public function beginChildren()
{
$this->event('beginChildren');
}
public function endChildren()
{
$this->testEndElement();
$this->event('endChildren');
}
private function testEndElement($depthOffset = 0)
{
$depth = $this->getDepth() + $depthOffset;
isset($this->elements[$depth]) || $this->elements[$depth] = 0;
$this->elements[$depth] && $this->event('endElement');
}
public function nextElement()
{
$this->testEndElement();
$this->event('{nextElement}');
$this->event('beginElement');
$this->elements[$this->getDepth()] = 1;
}
public function beginIteration()
{
$this->event('beginIteration');
}
public function endIteration()
{
$this->testEndElement();
$this->event('endIteration');
}
}
class ListDecorator
{
private $iterator;
public function __construct(RecursiveListIterator $iterator)
{
$this->iterator = $iterator;
}
public function inset($add = 0)
{
return str_repeat(' ', $this->iterator->getDepth()*2+$add);
}
public function beginElement()
{
printf("%s<li>\n", $this->inset(1));
}
public function endElement()
{
printf("%s</li>\n", $this->inset(1));
}
public function beginChildren()
{
printf("%s<ul>\n", $this->inset());
}
public function endChildren()
{
printf("%s</ul>\n", $this->inset());
}
public function beginIteration()
{
printf("%s<ul>\n", $this->inset());
}
public function endIteration()
{
printf("%s</ul>\n", $this->inset());
}
}
$root = new TreeNode($tree);
$it = new TreeNodesIterator(array($root));
$rit = new RecursiveListIterator($it);
$decor = new ListDecorator($rit);
$rit->addDecorator($decor);
foreach($rit as $item)
{
$inset = $decor->inset(2);
printf("%s%s\n", $inset, $item->getName());
}
Вихід:
<ul>
<li>
D
<ul>
<li>
G
<ul>
<li>
H
</li>
<li>
F
</li>
</ul>
</li>
<li>
E
<ul>
</li>
<li>
A
</li>
<li>
C
<ul>
<li>
B
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
Демо (варіант PHP 5.2)
Можливим варіантом може бути ітератор, який переходить на будь-який RecursiveIterator
і забезпечує ітерацію всіх подій, які можуть статися. Перемикач / корпус всередині циклу передпліччя може потім вирішувати події.
Пов'язані: