Un altro modo più semplificato per convertire la struttura piatta $tree
in una gerarchia. È necessario un solo array temporaneo per esporlo:
// 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);
Questo è tutto per ottenere la gerarchia in un array multidimensionale:
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
)
L'output è meno banale se si desidera evitare la ricorsione (può essere un peso con strutture grandi).
Ho sempre voluto risolvere il "dilemma" UL / LI per l'output di un array. Il dilemma è che ogni elemento non sa se i bambini seguiranno o quanti elementi precedenti devono essere chiusi. In un'altra risposta l'ho già risolto usando un RecursiveIteratorIterator
e cercando getDepth()
e altre meta-informazioni fornite dai miei scritti Iterator
: Ottenere un modello di insieme annidato in un <ul>
sottoalbero "chiuso" ma nascosto . Quella risposta mostra anche che con gli iteratori sei abbastanza flessibile.
Tuttavia quello era un elenco preordinato, quindi non sarebbe adatto per il tuo esempio. Inoltre ho sempre voluto risolvere questo problema per una sorta di struttura ad albero standard e HTML <ul>
e<li>
elementi.
Il concetto di base che mi è venuto in mente è il seguente:
TreeNode
- Astratta ogni elemento in un TreeNode
tipo semplice che può fornire il suo valore (ad esempio Name
) e se ha o meno figli.
TreeNodesIterator
- A RecursiveIterator
che è in grado di iterare su un insieme (array) di questi TreeNodes
. È abbastanza semplice in quanto il TreeNode
tipo sa già se ha figli e quali.
RecursiveListIterator
- A RecursiveIteratorIterator
che ha tutti gli eventi necessari quando itera ricorsivamente su qualsiasi tipo di RecursiveIterator
:
beginIteration
/ endIteration
- Inizio e fine dell'elenco principale.
beginElement
/ endElement
- Inizio e fine di ogni elemento.
beginChildren
/ endChildren
- Inizio e fine di ogni elenco figli. Questo RecursiveListIterator
fornisce solo questi eventi in una forma di chiamate di funzione. le liste figli, come è tipico per le <ul><li>
liste, vengono aperte e chiuse all'interno del suo <li>
elemento genitore . Pertanto l' endElement
evento viene generato dopo l' endChildren
evento corrispondente. Questo potrebbe essere modificato o reso configurabile per ampliare l'uso di questa classe. Gli eventi vengono quindi distribuiti come chiamate di funzione a un oggetto decoratore, per tenere le cose separate.
ListDecorator
- Una classe "decoratore" che è solo un destinatario degli eventi di RecursiveListIterator
.
Comincio con la logica di uscita principale. Preso l' $tree
array ora gerarchico , il codice finale è simile al seguente:
$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());
}
Guardare prima di lasciare nel ListDecorator
avvolge che semplicemente le <ul>
e <li>
elementi e sta decidendo su come la struttura della lista è in uscita:
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);
}
Il costruttore prende l'iteratore della lista su cui sta lavorando. inset
è solo una funzione di aiuto per un buon rientro dell'output. Il resto sono solo le funzioni di output per ogni evento:
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());
}
}
Con queste funzioni di output in mente, questo è di nuovo il wrap-up / loop dell'output principale, lo esamino passo dopo passo:
$root = new TreeNode($tree);
Crea la radice TreeNode
che verrà utilizzata per avviare l'iterazione su:
$it = new TreeNodesIterator(array($root));
Questo TreeNodesIterator
è un RecursiveIterator
che abilita l'iterazione ricorsiva sul singolo $root
nodo. Viene passato come un array perché quella classe ha bisogno di qualcosa su cui iterare e consente il riutilizzo con un insieme di figli che è anche un array di TreeNode
elementi.
$rit = new RecursiveListIterator($it);
Questo RecursiveListIterator
è un RecursiveIteratorIterator
che fornisce i suddetti eventi. Per utilizzarlo, è ListDecorator
necessario fornire solo a (la classe sopra) e assegnare addDecorator
:
$decor = new ListDecorator($rit);
$rit->addDecorator($decor);
Quindi tutto è impostato su di foreach
esso e genera ogni nodo:
foreach($rit as $item)
{
$inset = $decor->inset(1);
printf("%s%s\n", $inset, $item->getName());
}
Come mostra questo esempio, l'intera logica di output è incapsulata nella ListDecorator
classe e in questo singoloforeach
. L'intero attraversamento ricorsivo è stato completamente incapsulato in iteratori ricorsivi SPL che hanno fornito una procedura in pila, il che significa che internamente non vengono eseguite chiamate di funzione di ricorsione.
L'evento basato ListDecorator
consente di modificare l'output in modo specifico e di fornire più tipi di elenchi per la stessa struttura dati. È anche possibile modificare l'input poiché i dati dell'array sono stati incapsulatiTreeNode
.
L'esempio di codice completo:
<?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 (variante PHP 5.2)
Una possibile variante sarebbe un iteratore che itera su qualsiasi RecursiveIterator
e fornisce un'iterazione su tutti gli eventi che possono verificarsi. Un interruttore / caso all'interno del ciclo foreach potrebbe quindi gestire gli eventi.
Relazionato: