のフラット構造を$tree
階層に変換する、もう1つの、より単純化された方法。それを公開するには、一時配列が1つだけ必要です。
// 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
提供:にネストされたセットモデルを取得する<ul>
が、隠れサブツリーを「閉じました」。その答えは、イテレータを使用すると非常に柔軟であることも示しています。
ただし、これは事前にソートされたリストであるため、この例には適していません。さらに、私は常に、ある種の標準的なツリー構造とHTML <ul>
および<li>
要素についてこれを解決したいと思っていました。
私が思いついた基本的な概念は次のとおりです。
TreeNode
-各要素を、TreeNode
その値(例:)Name
および子があるかどうかを提供できる単純なタイプに抽象化します。
TreeNodesIterator
- RecursiveIterator
これらのセット(配列)を反復処理できるTreeNodes
。これは、TreeNode
型がすでに子を持っているかどうか、そしてどの子を持っているかを知っているので、かなり単純です。
RecursiveListIterator
- RecursiveIteratorIterator
任意の種類を再帰的に反復するときに必要なすべてのイベントがあるRecursiveIterator
:
beginIteration
/ endIteration
-メインリストの開始と終了。
beginElement
/ endElement
-各要素の始まりと終わり。
beginChildren
/ endChildren
-各子リストの始まりと終わり。これRecursiveListIterator
は、これらのイベントを関数呼び出しの形式でのみ提供します。子リストは、通常の<ul><li>
リストのように、その親<li>
要素内で開いたり閉じたりします。したがって、endElement
イベントは対応するendChildren
イベントの後に発生します。これは、このクラスの使用を拡大するために変更または構成可能にすることができます。イベントは、デコレータオブジェクトへの関数呼び出しとして分散され、物事を区別します。
ListDecorator
-のイベントの単なるレシーバである「デコレータ」クラス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());
}
最初に見てみましょう ListDecorator
、<ul>
と<li>
要素を単にラップし、リスト構造がどのように出力されるかを決定しているを調べます。
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
、単一$root
ノードでの再帰的な反復を可能にするです。そのクラスは繰り返し処理する必要があるため、配列として渡され、子のセットでも再利用できます。TreeNode
要素のます。
$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());
}
出力:
<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>
デモ(PHP 5.2バリアント)
可能なバリアントは、任意のオブジェクトに対してRecursiveIterator
反復し、発生する可能性のあるすべてのイベントに対して反復を提供する反復子です。その後、foreachループ内のスイッチ/ケースがイベントを処理できます。
関連: