Düz yapıyı $tree
hiyerarşiye dönüştürmenin daha basitleştirilmiş başka bir yolu . Göstermek için yalnızca bir geçici dizi gereklidir:
// 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);
Hepsi hiyerarşiyi çok boyutlu bir diziye sokmak için:
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
)
Özyinelemeden kaçınmak istiyorsanız çıktı daha az önemsizdir (büyük yapılarda bir yük olabilir).
Her zaman bir dizi çıktısını almak için UL / LI "ikilemini" çözmek istemişimdir. Buradaki ikilem, her maddenin çocukların takip edip etmeyeceğini veya önceki öğelerin kaçının kapatılması gerektiğini bilmemesidir. Başka bir cevapta, bunu a RecursiveIteratorIterator
ve arama getDepth()
ve kendi yazdığım diğer meta bilgileri kullanarak çözdüm Iterator
: İç içe geçmiş küme modelini, <ul>
ancak "kapalı" alt ağaçların içine almak . Bu cevap , yineleyicilerle oldukça esnek olduğunuzu da gösteriyor.
Ancak bu önceden sıralanmış bir listeydi, bu nedenle sizin örneğiniz için uygun olmayacaktır. Ek olarak, bunu her zaman bir tür standart ağaç yapısı ve HTML'ler <ul>
ve <li>
öğeler için çözmek istedim .
Bulduğum temel kavram şudur:
TreeNode
- Her bir öğeyi, TreeNode
değerini sağlayabilecek basit bir türe (örneğin Name
) ve çocukları olup olmadığına göre özetler.
TreeNodesIterator
- RecursiveIterator
Bunlardan oluşan bir dizi (dizi) üzerinde yineleme yapabilen bir TreeNodes
. Bu oldukça basit, çünkü TreeNode
tipin çocuğu olup olmadığını ve hangilerinin çocuk sahibi olduğunu zaten biliyor.
RecursiveListIterator
- RecursiveIteratorIterator
Herhangi bir tür üzerinde yinelemeli olarak yinelendiğinde gereken tüm olayları içeren bir RecursiveIterator
:
beginIteration
/ endIteration
- Ana listenin başlangıcı ve sonu.
beginElement
/ endElement
- Her öğenin başlangıcı ve bitişi.
beginChildren
/ endChildren
- Her çocuk listesinin başlangıcı ve bitişi. Bu RecursiveListIterator
, bu olayları yalnızca bir işlev çağrısı biçiminde sağlar. Listeler için tipik olduğu gibi alt <ul><li>
listeler, ana <li>
öğesi içinde açılır ve kapanır . Bu nedenle endElement
olay, ilgili olaydan sonra tetiklenir endChildren
. Bu, bu sınıfın kullanımını genişletmek için değiştirilebilir veya yapılandırılabilir hale getirilebilir. Olaylar, daha sonra işleri birbirinden ayırmak için bir dekoratör nesnesine işlev çağrıları olarak dağıtılır.
ListDecorator
- Olayların sadece alıcısı olan bir "dekoratör" sınıfı RecursiveListIterator
.
Ana çıktı mantığıyla başlıyorum. Şimdi hiyerarşik $tree
dizi alındığında, son kod aşağıdaki gibi görünür:
$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());
}
Öncelikle ListDecorator
, basitçe <ul>
ve <li>
öğelerini saran ve liste yapısının nasıl çıktılacağına karar verelim :
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);
}
Yapıcı, üzerinde çalıştığı liste yineleyicisini alır. inset
sadece çıktının hoş girintisi için yardımcı bir işlevdir. Geri kalanlar, her olay için yalnızca çıktı işlevleridir:
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());
}
}
Bu çıktı işlevlerini göz önünde bulundurarak, bu yine ana çıktı özetleme / döngüdür, adım adım ilerledim:
$root = new TreeNode($tree);
TreeNode
Yinelemeyi başlatmak için kullanılacak olan kökü oluşturun :
$it = new TreeNodesIterator(array($root));
Bu TreeNodesIterator
, RecursiveIterator
tek $root
düğüm üzerinde yinelemeli yinelemeyi etkinleştiren bir yöntemdir . Bir dizi olarak aktarılır çünkü bu sınıfın yinelenecek bir şeye ihtiyacı vardır ve aynı zamanda bir TreeNode
öğe dizisi olan bir dizi çocukla yeniden kullanıma izin verir .
$rit = new RecursiveListIterator($it);
Bu RecursiveListIterator
, RecursiveIteratorIterator
söz konusu olayları sağlayan bir şeydir. Bundan yararlanmak için, yalnızca ListDecorator
(yukarıdaki sınıf) sağlanmalı ve aşağıdakilerle atanmalıdır addDecorator
:
$decor = new ListDecorator($rit);
$rit->addDecorator($decor);
Sonra her şey hemen foreach
üzerine kurulur ve her bir düğümü çıktı olarak verir:
foreach($rit as $item)
{
$inset = $decor->inset(1);
printf("%s%s\n", $inset, $item->getName());
}
Bu örnekte gösterildiği gibi, tüm çıktı mantığı ListDecorator
sınıf ve bu tek içinde kapsüllenmiştir foreach
. Tüm özyinelemeli geçiş, yığınlanmış bir prosedür sağlayan SPL özyinelemeli yineleyicilerde tamamen kapsüllenmiştir, bu, dahili olarak hiçbir özyineleme işlevi çağrısının yapılmadığı anlamına gelir.
Olay tabanlı ListDecorator
, çıktıyı özel olarak değiştirmenize ve aynı veri yapısı için birden çok liste türü sağlamanıza olanak tanır. Dizi verilerinin içine kapsüllendiği için girişi değiştirmek bile mümkündür TreeNode
.
Tam kod örneği:
<?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());
}
Çıkış:
<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 varyantı)
Olası bir varyant, herhangi birinin üzerinde yineleyen RecursiveIterator
ve meydana gelebilecek tüm olaylar üzerinde bir yineleme sağlayan bir yineleyici olabilir. Foreach döngüsünün içindeki bir anahtar / durum olaylarla ilgilenebilir.
İlişkili: