Một cách khác, đơn giản hơn để chuyển đổi cấu trúc phẳng trong $tree
thành một hệ thống phân cấp. Chỉ cần một mảng tạm thời để hiển thị nó:
// 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);
Đó là tất cả để đưa hệ thống phân cấp vào một mảng đa chiều:
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
)
Đầu ra ít tầm thường hơn nếu bạn muốn tránh đệ quy (có thể là một gánh nặng với các cấu trúc lớn).
Tôi luôn muốn giải quyết "tình huống khó xử" UL / LI khi xuất ra một mảng. Vấn đề nan giải là mỗi mục không biết có trẻ em theo dõi hay không hoặc cần phải đóng bao nhiêu phần tử đứng trước. Trong một câu trả lời khác, tôi đã giải quyết vấn đề đó bằng cách sử dụng a RecursiveIteratorIterator
và tìm kiếm getDepth()
và các thông tin meta khác mà chính tôi đã viết Iterator
cung cấp: Đưa mô hình tập hợp lồng nhau vào một <ul>
nhưng ẩn cây con “đóng” . Câu trả lời đó cũng cho thấy rằng với trình vòng lặp, bạn khá linh hoạt.
Tuy nhiên, đó là danh sách được sắp xếp trước, vì vậy sẽ không phù hợp với ví dụ của bạn. Ngoài ra, tôi luôn muốn giải quyết vấn đề này cho một loại cấu trúc cây tiêu chuẩn và HTML <ul>
và <li>
các phần tử.
Khái niệm cơ bản tôi đã đưa ra như sau:
TreeNode
- Tóm tắt từng phần tử thành một TreeNode
kiểu đơn giản có thể cung cấp giá trị của nó (ví dụ Name
) và liệu nó có con hay không.
TreeNodesIterator
- A RecursiveIterator
có thể lặp lại trên một tập hợp (mảng) trong số này TreeNodes
. Điều đó khá đơn giản vì TreeNode
kiểu này đã biết nó có con nào không và con nào.
RecursiveListIterator
- A RecursiveIteratorIterator
có tất cả các sự kiện cần thiết khi nó lặp lại đệ quy trên bất kỳ loại nào RecursiveIterator
:
beginIteration
/ endIteration
- Bắt đầu và kết thúc danh sách chính.
beginElement
/ endElement
- Bắt đầu và kết thúc mỗi phần tử.
beginChildren
/ endChildren
- Bắt đầu và kết thúc mỗi danh sách trẻ em. Điều này RecursiveListIterator
chỉ cung cấp các sự kiện này dưới dạng lời gọi hàm. danh sách con, vì nó là điển hình cho <ul><li>
danh sách, được mở và đóng bên trong <li>
phần tử mẹ của nó . Do đó endElement
sự kiện được kích hoạt sau endChildren
sự kiện theo . Điều này có thể được thay đổi hoặc thực hiện cấu hình để mở rộng việc sử dụng lớp này. Sau đó, các sự kiện được phân phối dưới dạng lời gọi hàm đến một đối tượng decorator, để giữ cho mọi thứ khác biệt.
ListDecorator
- Một lớp "decorator" chỉ là người nhận các sự kiện của RecursiveListIterator
.
Tôi bắt đầu với logic đầu ra chính. Lấy $tree
mảng hiện có phân cấp , mã cuối cùng trông giống như sau:
$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());
}
Trước tiên hãy nhìn vào ListDecorator
mà chỉ đơn giản kết thúc tốt đẹp <ul>
và <li>
các yếu tố và là quyết định về cách thức cấu trúc danh sách là kết quả:
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);
}
Hàm khởi tạo lấy trình lặp danh sách mà nó đang làm việc. inset
chỉ là một chức năng trợ giúp để thụt lề đầu ra đẹp mắt. Phần còn lại chỉ là các hàm đầu ra cho mỗi sự kiện:
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());
}
}
Với các chức năng đầu ra này, đây là vòng lặp / kết thúc đầu ra chính một lần nữa, tôi sẽ đi qua từng bước:
$root = new TreeNode($tree);
Tạo thư mục gốc TreeNode
sẽ được sử dụng để bắt đầu lặp lại:
$it = new TreeNodesIterator(array($root));
Đây TreeNodesIterator
là một RecursiveIterator
cho phép lặp lại đệ quy trên một $root
nút. Nó được truyền dưới dạng một mảng vì lớp đó cần một thứ gì đó để lặp lại và cho phép sử dụng lại với một tập hợp con cũng là một mảng các TreeNode
phần tử.
$rit = new RecursiveListIterator($it);
Đây RecursiveListIterator
là một RecursiveIteratorIterator
cung cấp các sự kiện đã nói. Để sử dụng nó, chỉ ListDecorator
cần cung cấp một (lớp ở trên) và được gán với addDecorator
:
$decor = new ListDecorator($rit);
$rit->addDecorator($decor);
Sau đó, mọi thứ được thiết lập foreach
trên nó và xuất ra từng nút:
foreach($rit as $item)
{
$inset = $decor->inset(1);
printf("%s%s\n", $inset, $item->getName());
}
Như ví dụ này cho thấy, toàn bộ logic đầu ra được đóng gói trong ListDecorator
lớp và đơn này foreach
. Toàn bộ quá trình duyệt đệ quy đã được đóng gói đầy đủ vào các trình vòng lặp đệ quy SPL cung cấp một thủ tục xếp chồng lên nhau, có nghĩa là nội bộ không có lệnh gọi hàm đệ quy nào được thực hiện.
Sự kiện dựa trên ListDecorator
cho phép bạn sửa đổi đầu ra cụ thể và cung cấp nhiều loại danh sách cho cùng một cấu trúc dữ liệu. Thậm chí có thể thay đổi đầu vào vì dữ liệu mảng đã được đóng gói TreeNode
.
Ví dụ về mã đầy đủ:
<?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());
}
Ra ngoài:
<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 (biến thể PHP 5.2)
Một biến thể có thể có sẽ là một trình lặp lặp lại bất kỳ RecursiveIterator
và cung cấp một phép lặp trên tất cả các sự kiện có thể xảy ra. Sau đó, một công tắc / trường hợp bên trong vòng lặp foreach có thể giải quyết các sự kiện.
Có liên quan: