将平面结构$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
- RecursiveIterator
能够迭代这些集合(数组)的TreeNodes
。这非常简单,因为该TreeNode
类型已经知道它是否有子级以及哪些子级。
RecursiveListIterator
- RecursiveIteratorIterator
具有在任何类型上进行递归迭代时所需的所有事件的A 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
class和single中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
并提供对所有可能发生的事件的迭代。然后,foreach循环内的一个switch / case可以处理事件。
有关: