일련의 부모-자식 관계를 계층 적 트리로 변환 하시겠습니까?


100

가능한 한 적은 수의 계층 구조 트리 구조로 바꾸고 싶은 이름-부모 이름 쌍이 많이 있습니다. 예를 들어 다음과 같은 쌍이 될 수 있습니다.

Child : Parent
    H : G
    F : G
    G : D
    E : D
    A : E
    B : C
    C : E
    D : NULL

(a) 계층 적 트리로 변환해야합니다.

D
├── E
   ├── A
      └── B
   └── C   
└── G
    ├── F
    └── H

내가 원하는 최종 결과는 <ul>각각 <li>자식의 이름을 포함 하는 중첩 된 요소 집합입니다 .

쌍에는 불일치가 없으므로 (하위는 자신의 부모, 부모는 자식의 자식 등) 많은 최적화를 수행 할 수 있습니다.

PHP에서 어떻게 child => parent 쌍을 포함하는 배열에서 Nested 세트로 이동할 수 <ul>있습니까?

재귀가 관련되어 있다는 느낌이 있지만 충분히 생각할 수는 없습니다.

답변:


129

이를 위해서는 자식 / 부모 쌍을 트리 구조로 구문 분석하는 매우 기본적인 재귀 함수와이를 인쇄하기위한 또 다른 재귀 함수가 필요합니다. 하나의 기능만으로 충분하지만 명확성을 위해 두 가지가 있습니다 (이 답변의 끝에 결합 된 기능이 있습니다).

먼저 자식 / 부모 쌍의 배열을 초기화합니다.

$tree = array(
    'H' => 'G',
    'F' => 'G',
    'G' => 'D',
    'E' => 'D',
    'A' => 'E',
    'B' => 'C',
    'C' => 'E',
    'D' => null
);

그런 다음 해당 배열을 계층 트리 구조로 구문 분석하는 함수 :

function parseTree($tree, $root = null) {
    $return = array();
    # Traverse the tree and search for direct children of the root
    foreach($tree as $child => $parent) {
        # A direct child is found
        if($parent == $root) {
            # Remove item from tree (we don't need to traverse this again)
            unset($tree[$child]);
            # Append the child into result array and parse its children
            $return[] = array(
                'name' => $child,
                'children' => parseTree($tree, $child)
            );
        }
    }
    return empty($return) ? null : $return;    
}

그리고 그 트리를 순회하여 정렬되지 않은 목록을 출력하는 함수 :

function printTree($tree) {
    if(!is_null($tree) && count($tree) > 0) {
        echo '<ul>';
        foreach($tree as $node) {
            echo '<li>'.$node['name'];
            printTree($node['children']);
            echo '</li>';
        }
        echo '</ul>';
    }
}

그리고 실제 사용 :

$result = parseTree($tree);
printTree($result);

의 내용은 다음과 같습니다 $result.

Array(
    [0] => Array(
        [name] => D
        [children] => Array(
            [0] => Array(
                [name] => G
                [children] => Array(
                    [0] => Array(
                        [name] => H
                        [children] => NULL
                    )
                    [1] => Array(
                        [name] => F
                        [children] => NULL
                    )
                )
            )
            [1] => Array(
                [name] => E
                [children] => Array(
                    [0] => Array(
                        [name] => A
                        [children] => NULL
                    )
                    [1] => Array(
                        [name] => C
                        [children] => Array(
                            [0] => Array(
                                [name] => B
                                [children] => NULL
                            )
                        )
                    )
                )
            )
        )
    )
)

좀 더 효율성을 원하면 이러한 함수를 하나로 결합하여 반복 횟수를 줄일 수 있습니다.

function parseAndPrintTree($root, $tree) {
    $return = array();
    if(!is_null($tree) && count($tree) > 0) {
        echo '<ul>';
        foreach($tree as $child => $parent) {
            if($parent == $root) {                    
                unset($tree[$child]);
                echo '<li>'.$child;
                parseAndPrintTree($child, $tree);
                echo '</li>';
            }
        }
        echo '</ul>';
    }
}

이만큼 작은 데이터 세트에 8 번의 반복 만 저장하지만 더 큰 세트에서는 차이를 만들 수 있습니다.


2
타투. 트리의 html을 직접 에코하지 않고 모든 출력 html을 변수에 저장하고 반환하도록 printTree 함수를 어떻게 변경할 수 있습니까? 감사합니다
Enrique

안녕하세요, 함수 선언은 parseAndPrintTree ($ tree, $ root = null)이어야하고 재귀 호출은 parseAndPrintTree ($ child, $ tree);
감사

55

트리를 만드는 또 다른 함수 (재귀가 필요하지 않고 대신 참조를 사용함) :

$array = array('H' => 'G', 'F' => 'G', ..., 'D' => null);

function to_tree($array)
{
    $flat = array();
    $tree = array();

    foreach ($array as $child => $parent) {
        if (!isset($flat[$child])) {
            $flat[$child] = array();
        }
        if (!empty($parent)) {
            $flat[$parent][$child] =& $flat[$child];
        } else {
            $tree[$child] =& $flat[$child];
        }
    }

    return $tree;
}

다음과 같은 계층 적 배열을 반환합니다.

Array(
    [D] => Array(
        [G] => Array(
            [H] => Array()
            [F] => Array()
        )
        ...
    )
)

재귀 함수를 사용하여 HTML 목록으로 쉽게 인쇄 할 수 있습니다.


+1-매우 영리합니다. 재귀 솔루션이 더 논리적이라고 생각하지만. 그러나 나는 당신의 기능의 출력 형식을 선호합니다.
Eric

@Eric이 더 논리적입니까? 나는 다를 것을 간청한다. 재귀에는 '논리적'이 없습니다. 재귀 함수 / 호출을 구문 분석 할 때 OTOH에 심각한인지 오버 헤드가 있습니다. 명시적인 스택 할당이 없으면 매일 반복을 반복합니다.


29

의 플랫 구조를 $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>요소에 대해 이것을 해결하고 싶었습니다 .

내가 생각 해낸 기본 개념은 다음과 같습니다.

  1. TreeNode-각 요소를 TreeNode값 (예 :) Name과 자식이 있는지 여부를 제공 할 수 있는 간단한 유형 으로 추상화합니다 .
  2. TreeNodesIterator- RecursiveIterator이 세트 (배열)를 반복 할 수 있습니다 TreeNodes. TreeNode유형이 이미 자식이 있는지, 어떤 자식이 있는지 알고 있기 때문에 매우 간단 합니다.
  3. RecursiveListIterator-다음 RecursiveIteratorIterator과 같은 유형을 반복적으로 반복 할 때 필요한 모든 이벤트가 있는 A 입니다 RecursiveIterator.
    • beginIteration/ endIteration-기본 목록의 시작과 끝.
    • beginElement/ endElement-각 요소의 시작과 끝.
    • beginChildren/ endChildren-각 어린이 목록의 시작과 끝. 이것은 RecursiveListIterator함수 호출의 형태로만 이러한 이벤트를 제공합니다. 목록에서 일반적으로 사용되는 자식 목록은 <ul><li>부모 <li>요소 내에서 열리고 닫힙니다 . 따라서 endElement이벤트는 해당 이벤트 후에 시작됩니다 endChildren. 이 클래스의 사용을 확대하기 위해 변경하거나 구성 할 수 있습니다. 이벤트는 데코레이터 객체에 대한 함수 호출로 배포되어 사물을 분리합니다.
  4. 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);

이것은 RecursiveListIteratorA는 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 루프 내의 스위치 / 케이스가 이벤트를 처리 할 수 ​​있습니다.

관련 :


3
이 솔루션은 그대로 "잘 설계된"으로 - 그냥 같은 문제에 대한 이상 설계 솔루션처럼 보인다 - 어떻게 정확하게는 앞의 예보다 "더 방법을 간소화"입니다
앙드레

@Andre : IIRC 캡슐화 등급별. 다른 관련 답변에는 훨씬 더 작고 따라서 POV에 따라 "더 단순화"될 수있는 완전히 캡슐화되지 않은 코드 조각이 있습니다.
hakre 2015 년

@hakre "ListDecorator"클래스를 수정하여 트리 배열에서 가져 오는 LI에 'id'를 추가하려면 어떻게해야합니까?
Gangesh

1
@Gangesh : 노드 vistor를 사용하면 가장 쉽습니다. ^^ 농담으로 조금 간단하게 말하자면 데코레이터를 확장하고 beginElement ()를 편집하고 내부 반복자 (예는 inset () 메서드 참조)와 id 속성을 사용한 작업을 가져 오는 것입니다.
hakre

@hakre 감사합니다. 시도해 볼게요.
Gangesh

8

음, 먼저 키-값 쌍의 직선 배열을 계층 배열로 바꿉니다.

function convertToHeiarchical(array $input) {
    $parents = array();
    $root = array();
    $children = array();
    foreach ($input as $item) {
        $parents[$item['id']] = &$item;
        if ($item['parent_id']) {
            if (!isset($children[$item['parent_id']])) {
                $children[$item['parent_id']] = array();
            }
            $children[$item['parent_id']][] = &$item;
        } else {
            $root = $item['id'];
        }
    }
    foreach ($parents as $id => &$item) {
        if (isset($children[$id])) {
            $item['children'] = $children[$id];
        } else {
            $item['children'] = array();
        }
    }
    return $parents[$root];
}

그러면 parent_id 및 id가있는 플랫 배열을 계층 구조로 변환 할 수 있습니다.

$item = array(
    'id' => 'A',
    'blah' => 'blah',
    'children' => array(
        array(
            'id' => 'B',
            'blah' => 'blah',
            'children' => array(
                array(
                    'id' => 'C',
                    'blah' => 'blah',
                    'children' => array(),
                ),
             ),
            'id' => 'D',
            'blah' => 'blah',
            'children' => array(
                array(
                    'id' => 'E',
                    'blah' => 'blah',
                    'children' => array(),
                ),
            ),
        ),
    ),
);

그런 다음 렌더링 함수를 만듭니다.

function renderItem($item) {
    $out = "Your OUtput For Each Item Here";
    $out .= "<ul>";
    foreach ($item['children'] as $child) {
        $out .= "<li>".renderItem($child)."</li>";
    }
    $out .= "</ul>";
    return $out;
}

5

하지만 알렉산더 - 콘스탄티노 프 의 솔루션은 처음에는 쉽게 읽을으로 보이지 않을 수도 있습니다, 그것은 천재 기하 급수적으로 더 나은 성능의 측면에서,이 있었어야 최고의 답변으로 투표를 모두입니다.

감사합니다 친구,이 게시물에 제시된 두 가지 솔루션을 비교하기 위해 벤치 마크를 만들었습니다.

나는 변환해야하는 6 개의 레벨을 가진 @ 250k 플랫 트리를 가지고 있었고 더 나은 방법을 찾고 재귀 반복을 피했습니다.

재귀 대 참조 :

// Generate a 6 level flat tree
$root = null;
$lvl1 = 13;
$lvl2 = 11;
$lvl3 = 7;
$lvl4 = 5;
$lvl5 = 3;
$lvl6 = 1;    
$flatTree = [];
for ($i = 1; $i <= 450000; $i++) {
    if ($i % 3 == 0)  { $lvl5 = $i; $flatTree[$lvl6] = $lvl5; continue; }
    if ($i % 5 == 0)  { $lvl4 = $i; $flatTree[$lvl5] = $lvl4; continue; }
    if ($i % 7 == 0)  { $lvl3 = $i; $flatTree[$lvl3] = $lvl2; continue; }
    if ($i % 11 == 0) { $lvl2 = $i; $flatTree[$lvl2] = $lvl1; continue; }
    if ($i % 13 == 0) { $lvl1 = $i; $flatTree[$lvl1] = $root; continue; }
    $lvl6 = $i;
}

echo 'Array count: ', count($flatTree), PHP_EOL;

// Reference function
function treeByReference($flatTree)
{
    $flat = [];
    $tree = [];

    foreach ($flatTree as $child => $parent) {
        if (!isset($flat[$child])) {
            $flat[$child] = [];
        }
        if (!empty($parent)) {
            $flat[$parent][$child] =& $flat[$child];
        } else {
            $tree[$child] =& $flat[$child];
        }
    }

    return $tree;
}

// Recursion function
function treeByRecursion($flatTree, $root = null)
{
    $return = [];
    foreach($flatTree as $child => $parent) {
        if ($parent == $root) {
            unset($flatTree[$child]);
            $return[$child] = treeByRecursion($flatTree, $child);
        }
    }
    return $return ?: [];
}

// Benchmark reference
$t1 = microtime(true);
$tree = treeByReference($flatTree);
echo 'Reference: ', (microtime(true) - $t1), PHP_EOL;

// Benchmark recursion
$t2 = microtime(true);
$tree = treeByRecursion($flatTree);
echo 'Recursion: ', (microtime(true) - $t2), PHP_EOL;

출력은 다음과 같이 말합니다.

Array count: 255493
Reference: 0.3259289264679 (less than 0.4s)
Recursion: 6604.9865279198 (almost 2h)

2

UL 및 LI로 구문 분석하려면 다음과 같습니다.

$array = array (
    'H' => 'G'
    'F' => 'G'
    'G' => 'D'
    'E' => 'D'
    'A' => 'E'
    'B' => 'C'
    'C' => 'E'
    'D' => 'NULL'
);


recurse_uls ($array, 'NULL');

function recurse_uls ($array, $parent)
{
    echo '<ul>';
    foreach ($array as $c => $p)  {
        if ($p != $parent) continue;
        echo '<li>'.$c.'</li>';
        recurse_uls ($array, $c);
    }
    echo '</ul>';
}

하지만 어레이를 자주 반복 할 필요가없는 솔루션을보고 싶습니다.


2

내가 생각 해낸 것입니다.

$arr = array(
            'H' => 'G',
            'F' => 'G',
            'G' => 'D',
            'E' => 'D',
            'A' => 'E',
            'B' => 'C',
            'C' => 'E',
            'D' => null );

    $nested = parentChild($arr);
    print_r($nested);

    function parentChild(&$arr, $parent = false) {
      if( !$parent) { //initial call
         $rootKey = array_search( null, $arr);
         return array($rootKey => parentChild($arr, $rootKey));
      }else { // recursing through
        $keys = array_keys($arr, $parent);
        $piece = array();
        if($keys) { // found children, so handle them
          if( !is_array($keys) ) { // only one child
            $piece = parentChild($arr, $keys);
           }else{ // multiple children
             foreach( $keys as $key ){
               $piece[$key] = parentChild($arr, $key);
             }
           }
        }else {
           return $parent; //return the main tag (no kids)
        }
        return $piece; // return the array built via recursion
      }
    }

출력 :

Array
(
    [D] => Array
        (
            [G] => Array
                (
                    [H] => H
                    [F] => F
                )

            [E] => Array
                (
                    [A] => A
                    [C] => Array
                        (
                            [B] => B
                        )    
                )    
        )    
)

1

부모-자식 관계 중첩 배열
데이터베이스에서 모든 레코드를 가져와 중첩 배열을 만듭니다.

$data = SampleTable::find()->all();
$tree = buildTree($data);
print_r($tree);

public function buildTree(array $elements, $parentId = 0) {
    $branch = array();
    foreach ($elements as $element) {
        if ($element['iParentId'] == $parentId) {
            $children =buildTree($elements, $element['iCategoriesId']);
            if ($children) {
                $element['children'] = $children;
            }
            $branch[] = $element;
        }
    }
    return $branch;
}

범주 및 하위 범주 데이터를 json 형식으로 인쇄

public static function buildTree(array $elements, $parentId = 0){
    $branch = array();
    foreach($elements as $element){
        if($element['iParentId']==$parentId){
            $children =buildTree($elements, $element['iCategoriesId']);
            if ($children) {
                $element['children'] = $children;

            }
                $branch[] = array(
                    'iCategoriesId' => $element->iCategoriesId,
                    'iParentId'=>$element->iParentId,
                    'vCategoriesName'=>$element->vCategoriesName,
                    'children'=>$element->children,
            );
        }
    }
    return[
        $branch
    ];
}

0
$tree = array(
    'H' => 'G',
    'F' => 'G',
    'G' => 'D',
    'E' => 'D',
    'A' => 'E',
    'B' => 'C',
    'C' => 'E',
    'D' => null,
    'Z' => null,
    'MM' =>'Z',
    'KK' =>'Z',
    'MMM' =>'MM',
    // 'MM'=>'DDD'
);

$ aa = $ this-> parseTree ($ tree);

public function get_tress($tree,$key)
{

    $x=array();
    foreach ($tree as $keys => $value) {
        if($value==$key){
        $x[]=($keys);
        }
    }
    echo "<li>";
    foreach ($x as $ke => $val) {
    echo "<ul>";
        echo($val);
        $this->get_tress($tree,$val);
    echo "</ul>";
    }
    echo "</li>";


}
function parseTree($tree, $root = null) {

    foreach ($tree as $key => $value) {
        if($value==$root){

            echo "<ul>";
            echo($key);
            $this->get_tress($tree,$key);
            echo "</ul>";
        }
    }

0

오래된 질문이지만 나도 이것을해야했고 재귀를 사용한 예제는 나에게 두통을 주었다. 내 데이터베이스 locations에는 loca_idPK (자식) 및 자체 참조 loca_parent_id(부모) 인 테이블이 있습니다.

목적은이 구조를 HTML로 표현하는 것입니다. couyrse의 간단한 쿼리는 데이터를 고정 된 순서로 반환 할 수 있지만 이러한 데이터를 자연스럽게 표시하기에 충분하지 않다는 것을 알았습니다. 내가 정말로 원했던 것은 LEVEL디스플레이를 돕기 위해 Oracle 트리 워크 처리였습니다 .

각 항목을 고유하게 식별하기 위해 '경로'라는 아이디어를 사용하기로 결정했습니다. 예를 들면 :

경로별로 배열을 정렬하면 의미있는 표시를 처리하기가 더 쉬워집니다.

나는 연관 배열과 정렬의 사용이 작업의 반복적 인 복잡성을 숨기고 있기 때문에 속임수라는 것을 알고 있지만, 나에게는 이것이 더 간단 해 보입니다.

<table>
<?php
    
    $sql = "
    
    SELECT l.*,
           pl.loca_name parent_loca_name,
           '' loca_path
    FROM locations l
    LEFT JOIN locations pl ON l.loca_parent_id = pl.loca_id
    ORDER BY l.loca_parent_id, l.loca_id
    
    ";
    
    function print_row ( $rowdata )
    {
    ?>
                      <tr>
                          <td>
                              <?=$rowdata['loca_id']?>
                          </td>
                          <td>
                              <?=$rowdata['loca_path']?>
                          </td>
                          <td>
                              <?=$rowdata['loca_type']?>
                          </td>
                          <td>
                              <?=$rowdata['loca_status']?>
                          </td>
                      </tr>
    <?php
    
    }
    
    $stmt  = $dbh->prepare($sql);
    $stmt->execute();
    $result = $stmt->get_result();
    $data = $result->fetch_all(MYSQLI_ASSOC);
    
    $printed = array();
    
    // To get tree hierarchy usually means recursion of data.
    // Here we will try to use an associate array and set a
    // 'path' value to represent the hierarchy tree in one
    // pass. Sorting this array by the path value should give
    // a nice tree order and reference.
// The array key will be the unique id (loca_id) for each row.
// The value for each key will the complete row from the database.
// The row contains a element 'loca_path' - we will write the path
// for each row here. A child's path will be parent_path/child_name.
// For any child we encounter with a parent we look up the parents path
// using the loca_parent_id as the key.
// Caveat, although tested quickly, just make sure that all parents are
// returned first by the query.
    
    foreach ($data as $row)
    {
    
       if ( $row['loca_parent_id'] == '' ) // Root Parent
       {
          $row['loca_path'] = $row['loca_name'] . '/';
          $printed[$row['loca_id']] = $row;
       }
       else // Child/Sub-Parent
       {
          $row['loca_path'] = $printed[$row['loca_parent_id']]['loca_path'] . $row['loca_name'] . '/';
          $printed[$row['loca_id']] = $row;
       }
    }
    
    // Array with paths built, now sort then print
    
    array_multisort(array_column($printed, 'loca_path'), SORT_ASC, $printed);
    
    foreach ( $printed as $prow )
    {
       print_row ( $prow );
    }
    ?>
    </table>

-1

동적 트리보기 및 메뉴를 만드는 방법

1 단계 : 먼저 mysql 데이터베이스에 트 리뷰 테이블을 생성합니다. 이 테이블에는 4 개의 column이 있습니다 .id는 작업 ID이고 name은 작업 이름입니다.

-
-- Table structure for table `treeview_items`
--

CREATE TABLE IF NOT EXISTS `treeview_items` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(200) NOT NULL,
  `title` varchar(200) NOT NULL,
  `parent_id` varchar(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=7 ;

--
-- Dumping data for table `treeview_items`
--

INSERT INTO `treeview_items` (`id`, `name`, `title`, `parent_id`) VALUES
(1, 'task1', 'task1title', '2'),
(2, 'task2', 'task2title', '0'),
(3, 'task3', 'task1title3', '0'),
(4, 'task4', 'task2title4', '3'),
(5, 'task4', 'task1title4', '3'),
(6, 'task5', 'task2title5', '5');

2 단계 : 트리 뷰 재귀 메서드 아래에 트리 createTreeView () 메서드를 만들었습니다.이 메서드는 현재 작업 ID가 이전 작업 ID보다 크면 재귀를 호출합니다.

function createTreeView($array, $currentParent, $currLevel = 0, $prevLevel = -1) {

foreach ($array as $categoryId => $category) {

if ($currentParent == $category['parent_id']) {                       
    if ($currLevel > $prevLevel) echo " <ol class='tree'> "; 

    if ($currLevel == $prevLevel) echo " </li> ";

    echo '<li> <label for="subfolder2">'.$category['name'].'</label> <input type="checkbox" name="subfolder2"/>';

    if ($currLevel > $prevLevel) { $prevLevel = $currLevel; }

    $currLevel++; 

    createTreeView ($array, $categoryId, $currLevel, $prevLevel);

    $currLevel--;               
    }   

}

if ($currLevel == $prevLevel) echo " </li>  </ol> ";

}

3 단계 : 인덱스 파일을 만들어 트리보기를 표시합니다. 이것은 트 리뷰 예제의 메인 파일입니다. 여기에서 필요한 매개 변수로 createTreeView () 메소드를 호출합니다.

 <body>
<link rel="stylesheet" type="text/css" href="_styles.css" media="screen">
<?php
mysql_connect('localhost', 'root');
mysql_select_db('test');


$qry="SELECT * FROM treeview_items";
$result=mysql_query($qry);


$arrayCategories = array();

while($row = mysql_fetch_assoc($result)){ 
 $arrayCategories[$row['id']] = array("parent_id" => $row['parent_id'], "name" =>                       
 $row['name']);   
  }
?>
<div id="content" class="general-style1">
<?php
if(mysql_num_rows($result)!=0)
{
?>
<?php 

createTreeView($arrayCategories, 0); ?>
<?php
}
?>

</div>
</body>

4 단계 : CSS 파일 style.css 만들기 여기에서는 모든 CSS 관련 클래스를 작성합니다. 현재 주문 목록을 사용하여 트리보기를 만들고 있습니다. 여기에서 이미지 경로를 변경할 수도 있습니다.

img { border: none; }
input, select, textarea, th, td { font-size: 1em; }

/* CSS Tree menu styles */
ol.tree
{
    padding: 0 0 0 30px;
    width: 300px;
}
    li 
    { 
        position: relative; 
        margin-left: -15px;
        list-style: none;
    }
    li.file
    {
        margin-left: -1px !important;
    }
        li.file a
        {
            background: url(document.png) 0 0 no-repeat;
            color: #fff;
            padding-left: 21px;
            text-decoration: none;
            display: block;
        }
        li.file a[href *= '.pdf']   { background: url(document.png) 0 0 no-repeat; }
        li.file a[href *= '.html']  { background: url(document.png) 0 0 no-repeat; }
        li.file a[href $= '.css']   { background: url(document.png) 0 0 no-repeat; }
        li.file a[href $= '.js']        { background: url(document.png) 0 0 no-repeat; }
    li input
    {
        position: absolute;
        left: 0;
        margin-left: 0;
        opacity: 0;
        z-index: 2;
        cursor: pointer;
        height: 1em;
        width: 1em;
        top: 0;
    }
        li input + ol
        {
            background: url(toggle-small-expand.png) 40px 0 no-repeat;
            margin: -0.938em 0 0 -44px; /* 15px */
            height: 1em;
        }
        li input + ol > li { display: none; margin-left: -14px !important; padding-left: 1px; }
    li label
    {
        background: url(folder-horizontal.png) 15px 1px no-repeat;
        cursor: pointer;
        display: block;
        padding-left: 37px;
    }

    li input:checked + ol
    {
        background: url(toggle-small.png) 40px 5px no-repeat;
        margin: -1.25em 0 0 -44px; /* 20px */
        padding: 1.563em 0 0 80px;
        height: auto;
    }
        li input:checked + ol > li { display: block; margin: 0 0 0.125em;  /* 2px */}
        li input:checked + ol > li:last-child { margin: 0 0 0.063em; /* 1px */ }

자세한 내용은

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.