Kolejny, bardziej uproszczony sposób na przekształcenie płaskiej struktury w $tree
hierarchię. Do jej udostępnienia potrzebna jest tylko jedna tymczasowa tablica:
// 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);
To wszystko, aby uzyskać hierarchię w wielowymiarowej tablicy:
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
)
Wynik jest mniej trywialny, jeśli chcesz uniknąć rekursji (może być obciążeniem przy dużych strukturach).
Zawsze chciałem rozwiązać „dylemat” UL / LI dotyczący wyprowadzania tablicy. Dylemat polega na tym, że każda pozycja nie wie, czy dzieci będą kontynuowały lub ile elementów poprzedzających należy zamknąć. W innej odpowiedzi już to rozwiązałem, używając RecursiveIteratorIterator
szukania getDepth()
a i innych metainformacji, które dostarczył mój własny Iterator
: Przekształcanie zagnieżdżonego modelu zbioru w <ul>
ale ukrywające „zamknięte” poddrzewa . Ta odpowiedź pokazuje również, że dzięki iteratorom jesteś dość elastyczny.
Jednak była to wstępnie posortowana lista, więc nie byłaby odpowiednia dla twojego przykładu. Dodatkowo zawsze chciałem rozwiązać to dla pewnego rodzaju standardowej struktury drzewa oraz HTML <ul>
i <li>
elementów.
Podstawowa koncepcja, którą wymyśliłem, jest następująca:
TreeNode
- Abstrahuje każdy element do prostego TreeNode
typu, który może zapewnić jego wartość (np. Name
) I określa, czy ma on dzieci.
TreeNodesIterator
- A RecursiveIterator
która jest w stanie wykonać iterację po zestawie (tablicy) tych TreeNodes
. Jest to dość proste, ponieważ TreeNode
typ już wie, czy ma dzieci i które z nich.
RecursiveListIterator
- A, RecursiveIteratorIterator
która zawiera wszystkie potrzebne zdarzenia, gdy rekurencyjnie wykonuje iterację dowolnego rodzaju RecursiveIterator
:
beginIteration
/ endIteration
- Początek i koniec głównej listy.
beginElement
/ endElement
- Początek i koniec każdego elementu.
beginChildren
/ endChildren
- Początek i koniec każdej listy dzieci. Zapewnia to RecursiveListIterator
tylko te zdarzenia w postaci wywołań funkcji. listy dzieci, jak to jest typowe dla <ul><li>
list, są otwierane i zamykane wewnątrz <li>
elementu nadrzędnego . Dlatego endElement
zdarzenie jest uruchamiane po odpowiednim endChildren
zdarzeniu. Można to zmienić lub skonfigurować w celu rozszerzenia zastosowania tej klasy. Zdarzenia są następnie dystrybuowane jako wywołania funkcji do obiektu dekoratora, aby oddzielić rzeczy.
ListDecorator
- Klasa „dekorator” będąca tylko odbiorcą wydarzeń z RecursiveListIterator
.
Zacznę od głównej logiki wyjściowej. Biorąc pod uwagę hierarchiczną $tree
tablicę now , ostateczny kod wygląda następująco:
$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());
}
Wygląd Zacznijmy Into the ListDecorator
że po prostu opakowuje <ul>
i <li>
elementy i decyduje o tym, jak struktura lista jest wyjście:
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);
}
Konstruktor pobiera iterator listy, nad którym pracuje. inset
jest tylko funkcją pomocniczą do ładnego wcięcia danych wyjściowych. Reszta to tylko funkcje wyjściowe dla każdego zdarzenia:
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());
}
}
Mając na uwadze te funkcje wyjściowe, jest to ponownie główne zawijanie / pętla wyjścia, przechodzę przez to krok po kroku:
$root = new TreeNode($tree);
Utwórz root, TreeNode
który będzie używany do rozpoczęcia iteracji po:
$it = new TreeNodesIterator(array($root));
To TreeNodesIterator
jest, RecursiveIterator
które umożliwia rekurencyjną iterację w pojedynczym $root
węźle. Jest przekazywana jako tablica, ponieważ ta klasa wymaga czegoś do iteracji i umożliwia ponowne użycie z zestawem elementów potomnych, który jest również tablicą TreeNode
elementów.
$rit = new RecursiveListIterator($it);
To RecursiveListIterator
jest, RecursiveIteratorIterator
które zapewnia wspomniane zdarzenia. Aby z niego skorzystać, wystarczy ListDecorator
podać (klasę powyżej) i przypisać addDecorator
:
$decor = new ListDecorator($rit);
$rit->addDecorator($decor);
Następnie wszystko jest ustawione tak, aby tuż foreach
nad nim i wyprowadzało każdy węzeł:
foreach($rit as $item)
{
$inset = $decor->inset(1);
printf("%s%s\n", $inset, $item->getName());
}
Jak pokazuje ten przykład, cała logika wyjściowa jest zawarta w ListDecorator
klasie i tym pojedynczym foreach
. Całe przechodzenie rekurencyjne zostało w pełni zahermetyzowane w iteratorach rekurencyjnych SPL, które zapewniały procedurę stosową, co oznacza, że wewnętrznie nie są wykonywane żadne wywołania funkcji rekurencyjnych.
Oparta na zdarzeniach ListDecorator
umożliwia konkretną modyfikację danych wyjściowych i zapewnienie wielu typów list dla tej samej struktury danych. Można nawet zmienić dane wejściowe, gdy dane tablicy zostały hermetyzowane TreeNode
.
Przykład pełnego kodu:
<?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());
}
Outpupt:
<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>
Demo (wariant PHP 5.2)
Możliwym wariantem byłby iterator, który iteruje po każdym RecursiveIterator
i zapewnia iterację po wszystkich zdarzeniach, które mogą wystąpić. Przełącznik / przypadek wewnątrz pętli foreach mógłby wtedy obsługiwać zdarzenia.
Związane z: