Cara lain yang lebih disederhanakan untuk mengubah struktur datar $treemenjadi hierarki. Hanya satu larik sementara yang diperlukan untuk mengeksposnya:
// 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);
Itu saja untuk membuat hierarki menjadi array multidimensi:
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
)
Outputnya kurang sepele jika Anda ingin menghindari rekursi (bisa menjadi beban dengan struktur besar).
Saya selalu ingin memecahkan "dilema" UL / LI untuk mengeluarkan sebuah array. Dilemanya adalah, setiap item tidak mengetahui apakah anak akan menindaklanjuti atau tidak atau berapa banyak elemen sebelumnya yang perlu ditutup. Dalam jawaban lain saya sudah memecahkannya dengan menggunakan RecursiveIteratorIteratordan mencari getDepth()dan meta-informasi lain yang saya tulis sendiri Iterator: Mendapatkan model set bersarang ke dalam <ul>tetapi menyembunyikan subpohon "tertutup" . Itu jawaban menunjukkan juga bahwa dengan iterator Anda cukup fleksibel.
Namun itu adalah daftar yang telah diurutkan sebelumnya, jadi tidak akan cocok untuk contoh Anda. Selain itu saya selalu ingin menyelesaikan ini untuk semacam struktur pohon standar dan HTML <ul>dan <li>elemen.
Konsep dasar yang saya kemukakan adalah sebagai berikut:
TreeNode- Mengabstraksi setiap elemen menjadi TreeNodetipe sederhana yang dapat memberikan nilainya (misalnya Name) dan memiliki turunan atau tidak.
TreeNodesIterator- A RecursiveIteratoryang mampu melakukan iterasi atas satu set (array) ini TreeNodes. Itu cukup sederhana karena TreeNodetipe sudah tahu apakah ia memiliki anak dan yang mana.
RecursiveListIterator- A RecursiveIteratorIteratoryang memiliki semua peristiwa yang diperlukan saat iterasi secara rekursif melalui segala jenis RecursiveIterator:
beginIteration/ endIteration- Awal dan akhir daftar utama.
beginElement/ endElement- Awal dan akhir setiap elemen.
beginChildren/ endChildren- Awal dan akhir setiap daftar anak. Ini RecursiveListIteratorhanya menyediakan acara ini dalam bentuk pemanggilan fungsi. daftar anak-anak, seperti yang biasa untuk <ul><li>daftar, dibuka dan ditutup di dalam <li>elemen induknya . Oleh karena itu endElementacara tersebut dipecat setelah endChildrenacara yang sesuai . Ini dapat diubah atau dibuat dapat dikonfigurasi untuk memperluas penggunaan kelas ini. Peristiwa didistribusikan sebagai panggilan fungsi ke objek dekorator kemudian, untuk memisahkan hal-hal.
ListDecorator- Kelas "dekorator" yang hanya menerima peristiwa RecursiveListIterator.
Saya mulai dengan logika keluaran utama. Diambil $treearray hirarkis sekarang , kode akhirnya terlihat seperti berikut:
$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());
}
Penampilan let pertama ke dalam ListDecoratoryang hanya membungkus <ul>dan <li>elemen dan memutuskan tentang bagaimana struktur daftar adalah output:
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);
}
Konstruktor mengambil iterator daftar yang sedang dikerjakannya. insethanyalah fungsi pembantu untuk lekukan yang bagus pada keluaran. Sisanya hanyalah fungsi keluaran untuk setiap acara:
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());
}
}
Dengan mengingat fungsi-fungsi output ini, ini adalah output utama / loop lagi, saya melakukannya selangkah demi selangkah:
$root = new TreeNode($tree);
Buat root TreeNodeyang akan digunakan untuk memulai iterasi:
$it = new TreeNodesIterator(array($root));
Ini TreeNodesIteratoradalah RecursiveIteratoryang memungkinkan iterasi rekursif melalui satu $rootnode. Itu diteruskan sebagai array karena kelas itu membutuhkan sesuatu untuk diiterasi dan memungkinkan penggunaan kembali dengan satu set turunan yang juga merupakan array TreeNodeelemen.
$rit = new RecursiveListIterator($it);
Ini RecursiveListIteratoradalah RecursiveIteratorIteratoryang menyediakan acara tersebut. Untuk memanfaatkannya, hanya ListDecoratorperlu disediakan (kelas di atas) dan ditugaskan dengan addDecorator:
$decor = new ListDecorator($rit);
$rit->addDecorator($decor);
Kemudian semuanya diatur ke foreachatasnya dan mengeluarkan setiap node:
foreach($rit as $item)
{
$inset = $decor->inset(1);
printf("%s%s\n", $inset, $item->getName());
}
Seperti yang ditunjukkan contoh ini, seluruh logika output dikemas dalam ListDecoratorkelas dan single ini foreach. Seluruh traversal rekursif telah sepenuhnya dikemas ke dalam iterator rekursif SPL yang menyediakan prosedur bertumpuk, yang berarti secara internal tidak ada panggilan fungsi rekursi yang dilakukan.
Berbasis peristiwa ListDecoratormemungkinkan Anda mengubah keluaran secara khusus dan menyediakan beberapa jenis daftar untuk struktur data yang sama. Bahkan dimungkinkan untuk mengubah input karena data array telah dienkapsulasi TreeNode.
Contoh kode lengkap:
<?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 (varian PHP 5.2)
Varian yang mungkin adalah iterator yang melakukan iterasi atas apa pun RecursiveIteratordan memberikan iterasi atas semua peristiwa yang dapat terjadi. Sebuah switch / case di dalam foreach loop kemudian dapat menangani kejadian tersebut.
Terkait: