의 플랫 구조를 $tree
계층 구조로 변환하는 더 간단한 또 다른 방법 입니다. 노출하려면 임시 배열이 하나만 필요합니다.
// 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 "딜레마"를 항상 해결하고 싶었습니다. 딜레마는 각 항목이 자식이 후속 조치를 취할 것인지 또는 닫아야하는 선행 요소의 수를 알지 못한다는 것입니다. 또 다른 대답에서 나는 이미 a를 사용하고 내가 작성한 다른 메타 정보를 RecursiveIteratorIterator
찾아서 해결했습니다 . 중첩 된 세트 모델을 "닫힌"하위 트리 로 가져 오기 . 그 대답 은 반복자를 사용하면 매우 유연하다는 것을 보여줍니다.getDepth()
Iterator
<ul>
그러나 이것은 미리 정렬 된 목록이므로 귀하의 예에는 적합하지 않을 것입니다. 또한 나는 항상 일종의 표준 트리 구조와 HTML <ul>
및 <li>
요소에 대해 이것을 해결하고 싶었습니다 .
내가 생각 해낸 기본 개념은 다음과 같습니다.
TreeNode
-각 요소를 TreeNode
값 (예 :) Name
과 자식이 있는지 여부를 제공 할 수 있는 간단한 유형 으로 추상화합니다 .
TreeNodesIterator
- RecursiveIterator
이 세트 (배열)를 반복 할 수 있습니다 TreeNodes
. TreeNode
유형이 이미 자식이 있는지, 어떤 자식이 있는지 알고 있기 때문에 매우 간단 합니다.
RecursiveListIterator
-다음 RecursiveIteratorIterator
과 같은 유형을 반복적으로 반복 할 때 필요한 모든 이벤트가 있는 A 입니다 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
A는 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 루프 내의 스위치 / 케이스가 이벤트를 처리 할 수 있습니다.
관련 :