Cara lain yang lebih disederhanakan untuk mengubah struktur datar $tree
menjadi 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 RecursiveIteratorIterator
dan 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 TreeNode
tipe sederhana yang dapat memberikan nilainya (misalnya Name
) dan memiliki turunan atau tidak.
TreeNodesIterator
- A RecursiveIterator
yang mampu melakukan iterasi atas satu set (array) ini TreeNodes
. Itu cukup sederhana karena TreeNode
tipe sudah tahu apakah ia memiliki anak dan yang mana.
RecursiveListIterator
- A RecursiveIteratorIterator
yang 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 RecursiveListIterator
hanya 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 endElement
acara tersebut dipecat setelah endChildren
acara 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 $tree
array 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 ListDecorator
yang 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. inset
hanyalah 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 TreeNode
yang akan digunakan untuk memulai iterasi:
$it = new TreeNodesIterator(array($root));
Ini TreeNodesIterator
adalah RecursiveIterator
yang memungkinkan iterasi rekursif melalui satu $root
node. Itu diteruskan sebagai array karena kelas itu membutuhkan sesuatu untuk diiterasi dan memungkinkan penggunaan kembali dengan satu set turunan yang juga merupakan array TreeNode
elemen.
$rit = new RecursiveListIterator($it);
Ini RecursiveListIterator
adalah RecursiveIteratorIterator
yang menyediakan acara tersebut. Untuk memanfaatkannya, hanya ListDecorator
perlu disediakan (kelas di atas) dan ditugaskan dengan addDecorator
:
$decor = new ListDecorator($rit);
$rit->addDecorator($decor);
Kemudian semuanya diatur ke foreach
atasnya 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 ListDecorator
kelas 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 ListDecorator
memungkinkan 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 RecursiveIterator
dan memberikan iterasi atas semua peristiwa yang dapat terjadi. Sebuah switch / case di dalam foreach loop kemudian dapat menangani kejadian tersebut.
Terkait: