另一種更簡化的方式將$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
提供的其他元信息:Getting nested set model into a <ul>
but hiding 「closed」 subtrees。 answer顯示,與迭代器,你很靈活。
但是這是一個預先排序的列表,所以不適合您的示例。此外,我一直想解決這個問題的一種標準樹結構和HTML的<ul>
和<li>
元素。
我想出的基本概念是:
TreeNode
- 文摘每個元素成簡單TreeNode
類型,可以提供它的值(例如Name
),以及是否有孩子。
TreeNodesIterator
- A RecursiveIterator
能夠遍歷這些TreeNodes
的集合(數組)。這很簡單,因爲TreeNode
類型已經知道它是否有孩子和哪些孩子。
RecursiveListIterator
- 有當遞歸遍歷任何一種RecursiveIterator
所需的所有事件RecursiveIteratorIterator
:
beginIteration
/endIteration
- 開始和主列表的末尾。
beginElement
/endElement
- 每個元素的開始和結束。
beginChildren
/endChildren
- 每個子列表的開始和結束。 這個RecursiveListIterator
只以函數調用的形式提供這些事件。兒童名單,因爲它是典型的<ul><li>
名單,被打開和關閉它的父母<li>
元素。因此endElement
事件在相應的endChildren
事件之後被觸發。這可以改變或可配置,以擴大使用這個類。這些事件是作爲函數調用分發給裝飾器對象的,然後將事情分開。
ListDecorator
- A 「裝飾」 類,它僅僅是一個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());
}
首先,讓我們看看,簡單地包裝了<ul>
和<li>
元素,並在判決的表結構如何輸出的ListDecorator
:
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
,使遞歸迭代過第e單一$root
節點。它作爲一個數組傳遞,因爲該類需要迭代並允許與一組子元素重用,這也是一個元素數組。
$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
類和此單個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());
}
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 (PHP 5.2 variant)
一種可能的變體將是遍歷任何RecursiveIterator
並提供對所有事件的迭代的迭代器可能發生。然後,foreach循環內的開關/情況可以處理事件。
相關:
大肚。我怎樣才能改變printTree函數不直接回應樹的html,但保存所有的輸出html到一個變量並返回它?謝謝 – Enrique 2012-12-17 13:58:59
嗨,我認爲函數聲明必須是parseAndPrintTree($ tree,$ root = null) 並且遞歸調用應該是parseAndPrintTree($ child,$ tree); 致以問候 – razor7 2014-04-25 20:52:02