Otra forma más simplificada de convertir la estructura plana $tree
en una jerarquía. Solo se necesita una matriz temporal para exponerlo:
// 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);
Eso es todo para colocar la jerarquía en una matriz multidimensional:
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
)
El resultado es menos trivial si desea evitar la recursividad (puede ser una carga con estructuras grandes).
Siempre quise resolver el "dilema" de UL / LI para generar una matriz. El dilema es que cada elemento no sabe si los niños seguirán o no o cuántos elementos precedentes deben cerrarse. En otra respuesta, ya resolví eso usando un RecursiveIteratorIterator
y buscando getDepth()
y otra metainformación que Iterator
proporcionó mi propio escrito : Obtener un modelo de conjunto anidado en un <ul>
subárbol "cerrado" pero oculto . Esa respuesta también muestra que con los iteradores eres bastante flexible.
Sin embargo, esa era una lista ordenada previamente, por lo que no sería adecuada para su ejemplo. Además, siempre quise resolver esto para una especie de estructura de árbol estándar y HTML <ul>
y <li>
elementos.
El concepto básico que se me ocurrió es el siguiente:
TreeNode
- Resumen cada elemento en un TreeNode
tipo simple que puede proporcionar su valor (por ejemplo Name
) y si tiene hijos o no.
TreeNodesIterator
- A RecursiveIterator
que puede iterar sobre un conjunto (matriz) de estos TreeNodes
. Eso es bastante simple porque el TreeNode
tipo ya sabe si tiene hijos y cuáles.
RecursiveListIterator
- A RecursiveIteratorIterator
que tiene todos los eventos necesarios cuando itera de forma recursiva sobre cualquier tipo de RecursiveIterator
:
beginIteration
/ endIteration
- Comienzo y final de la lista principal.
beginElement
/ endElement
- Comienzo y final de cada elemento.
beginChildren
/ endChildren
- Comienzo y final de cada lista de niños. Esto RecursiveListIterator
solo proporciona estos eventos en forma de llamadas a funciones. las listas secundarias, como es típico en las <ul><li>
listas, se abren y cierran dentro de su <li>
elemento padre . Por lo tanto, el endElement
evento se dispara después del endChildren
evento correspondiente. Esto podría cambiarse o configurarse para ampliar el uso de esta clase. Los eventos se distribuyen como llamadas de función a un objeto decorador para mantener las cosas separadas.
ListDecorator
- Una clase "decoradora" que es solo un receptor de los eventos de RecursiveListIterator
.
Empiezo con la lógica de salida principal. Tomada la $tree
matriz ahora jerárquica , el código final se parece a lo siguiente:
$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());
}
En primer lugar mirada nos dejó en el ListDecorator
que simplemente se envuelve las <ul>
y los <li>
elementos y está decidiendo sobre cómo la estructura de la lista de salida es:
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);
}
El constructor toma el iterador de lista en el que está trabajando. inset
es solo una función auxiliar para una buena sangría de la salida. El resto son solo las funciones de salida para cada 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 estas funciones de salida en mente, este es el ciclo / resumen de salida principal nuevamente, lo reviso paso a paso:
$root = new TreeNode($tree);
Cree la raíz TreeNode
que se utilizará para iniciar la iteración en:
$it = new TreeNodesIterator(array($root));
Esta TreeNodesIterator
es una RecursiveIterator
que permite la iteración recursiva sobre un solo $root
nodo. Se pasa como una matriz porque esa clase necesita algo sobre lo que iterar y permite la reutilización con un conjunto de elementos secundarios que también es una matriz de TreeNode
elementos.
$rit = new RecursiveListIterator($it);
Este RecursiveListIterator
es un RecursiveIteratorIterator
que proporciona dichos eventos. Para hacer uso de él, solo es ListDecorator
necesario proporcionar un (la clase anterior) y asignarle addDecorator
:
$decor = new ListDecorator($rit);
$rit->addDecorator($decor);
Luego, todo está configurado para pasarlo foreach
y generar cada nodo:
foreach($rit as $item)
{
$inset = $decor->inset(1);
printf("%s%s\n", $inset, $item->getName());
}
Como muestra este ejemplo, toda la lógica de salida está encapsulada en la ListDecorator
clase y este single foreach
. Todo el recorrido recursivo se ha encapsulado completamente en iteradores recursivos SPL que proporcionaron un procedimiento apilado, lo que significa que internamente no se realizan llamadas a funciones de recursividad.
El basado en eventos le ListDecorator
permite modificar la salida específicamente y proporcionar múltiples tipos de listas para la misma estructura de datos. Incluso es posible cambiar la entrada ya que se han encapsulado los datos de la matriz TreeNode
.
El ejemplo de código 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>
Demostración (variante PHP 5.2)
Una posible variante sería un iterador que itera sobre cualquiera RecursiveIterator
y proporciona una iteración sobre todos los eventos que pueden ocurrir. Un interruptor / caso dentro del bucle foreach podría lidiar con los eventos.
Relacionado: