Eine andere, vereinfachte Möglichkeit, die flache Struktur $tree
in eine Hierarchie umzuwandeln . Es wird nur ein temporäres Array benötigt, um es verfügbar zu machen:
// 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);
Das ist alles, um die Hierarchie in ein mehrdimensionales Array zu bringen:
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
)
Die Ausgabe ist weniger trivial, wenn Sie eine Rekursion vermeiden möchten (kann bei großen Strukturen eine Belastung sein).
Ich wollte immer das UL / LI- "Dilemma" für die Ausgabe eines Arrays lösen. Das Dilemma besteht darin, dass jedes Element nicht weiß, ob Kinder nachverfolgen oder wie viele vorhergehende Elemente geschlossen werden müssen. In einer anderen Antwort habe ich das bereits gelöst, indem ich eine RecursiveIteratorIterator
und nach getDepth()
anderen Metainformationen gesucht und gesucht habe , die ich selbst geschrieben Iterator
habe: Verschachteltes Mengenmodell in einen <ul>
aber ausgeblendeten „geschlossenen“ Teilbaum zu bringen . Diese Antwort zeigt auch, dass Sie mit Iteratoren ziemlich flexibel sind.
Dies war jedoch eine vorsortierte Liste und daher für Ihr Beispiel nicht geeignet. Außerdem wollte ich dies immer für eine Art Standardbaumstruktur sowie HTMLs <ul>
und <li>
Elemente lösen .
Das Grundkonzept, das ich mir ausgedacht habe, ist das folgende:
TreeNode
- Abstrakt jedes Element in einen einfachen TreeNode
Typ, der seinen Wert liefern kann (z. B. Name
) und ob es untergeordnete Elemente hat oder nicht.
TreeNodesIterator
- A RecursiveIterator
, das über eine Menge (Array) von diesen iterieren kann TreeNodes
. Das ist ziemlich einfach, da der TreeNode
Typ bereits weiß, ob und welche Kinder er hat.
RecursiveListIterator
- A RecursiveIteratorIterator
, das alle Ereignisse enthält, die erforderlich sind, wenn es rekursiv über eine der folgenden Arten iteriert RecursiveIterator
:
beginIteration
/ endIteration
- Anfang und Ende der Hauptliste.
beginElement
/ endElement
- Anfang und Ende jedes Elements.
beginChildren
/ endChildren
- Anfang und Ende jeder Kinderliste. Dies RecursiveListIterator
stellt diese Ereignisse nur in Form von Funktionsaufrufen bereit. Untergeordnete Listen werden, wie es für <ul><li>
Listen typisch ist , innerhalb des übergeordneten <li>
Elements geöffnet und geschlossen . Daher wird das endElement
Ereignis nach dem entsprechenden endChildren
Ereignis ausgelöst . Dies kann geändert oder konfigurierbar gemacht werden, um die Verwendung dieser Klasse zu erweitern. Die Ereignisse werden dann als Funktionsaufrufe an ein Dekorationsobjekt verteilt, um die Dinge auseinander zu halten.
ListDecorator
- Eine "Dekorateur" -Klasse, die nur ein Empfänger der Ereignisse von ist RecursiveListIterator
.
Ich beginne mit der Hauptausgangslogik. In dem jetzt hierarchischen $tree
Array sieht der endgültige Code wie folgt aus:
$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());
}
Lassen Sie uns zunächst Blick in die , ListDecorator
die einfach wickelt die <ul>
und <li>
Elemente und ist die Entscheidung darüber , wie die Listenstruktur ausgegeben:
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);
}
Der Konstruktor verwendet den Listeniterator, an dem er arbeitet. inset
ist nur eine Hilfsfunktion für eine schöne Einrückung der Ausgabe. Der Rest sind nur die Ausgabefunktionen für jedes Ereignis:
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());
}
}
In Anbetracht dieser Ausgabefunktionen ist dies wieder die Hauptausgabe-Zusammenfassung / Schleife. Ich gehe sie Schritt für Schritt durch:
$root = new TreeNode($tree);
Erstellen Sie die Wurzel TreeNode
, mit der die Iteration gestartet wird:
$it = new TreeNodesIterator(array($root));
Dies TreeNodesIterator
ist eine , RecursiveIterator
die rekursive Iteration über den einzelnen ermöglicht $root
Knoten. Es wird als Array übergeben, da diese Klasse etwas zum Durchlaufen benötigt und die Wiederverwendung mit einer Reihe von untergeordneten TreeNode
Elementen ermöglicht, die auch ein Array von Elementen sind.
$rit = new RecursiveListIterator($it);
Dies RecursiveListIterator
ist eine RecursiveIteratorIterator
, die die genannten Ereignisse bereitstellt. Um davon Gebrauch zu machen, muss nur ein ListDecorator
(die obige Klasse) bereitgestellt und zugewiesen werden mit addDecorator
:
$decor = new ListDecorator($rit);
$rit->addDecorator($decor);
Dann wird alles so eingestellt, dass es direkt foreach
darüber liegt und jeden Knoten ausgibt:
foreach($rit as $item)
{
$inset = $decor->inset(1);
printf("%s%s\n", $inset, $item->getName());
}
Wie dieses Beispiel zeigt, ist die gesamte Ausgabelogik in der ListDecorator
Klasse und dieser einzelnen gekapselt foreach
. Die gesamte rekursive Durchquerung wurde vollständig in rekursive SPL-Iteratoren eingekapselt, die eine gestapelte Prozedur bereitstellten. Dies bedeutet, dass intern keine Rekursionsfunktionsaufrufe ausgeführt werden.
Mit der ereignisbasierten Funktion ListDecorator
können Sie die Ausgabe spezifisch ändern und mehrere Listentypen für dieselbe Datenstruktur bereitstellen. Es ist sogar möglich, die Eingabe zu ändern, wenn die Array-Daten eingekapselt wurden TreeNode
.
Das vollständige Codebeispiel:
<?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());
}
Ausbruch:
<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 (PHP 5.2 Variante)
Eine mögliche Variante wäre ein Iterator, der RecursiveIterator
über alle Ereignisse iteriert und eine Iteration über alle Ereignisse bereitstellt, die auftreten können. Ein Schalter / Fall innerhalb der foreach-Schleife könnte sich dann mit den Ereignissen befassen.
Verbunden: