Перетворити ряд відносин батько-дитина в ієрархічне дерево?


100

У мене є купа пар з іменами-батьками, які я хотів би перетворити на якомога менше герархічних деревних структур. Так, наприклад, це можуть бути пари:

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

Яке потрібно перетворити на (а) герархічне дерево (и):

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

Кінцевий результат, який я хочу, - це вкладений набір <ul>елементів, кожен з яких <li>містить ім’я дитини.

У спарюваннях немає невідповідностей (дитина - це власний батько, батько - дитина дитини тощо), тому, певно, можна зробити оптимізацію.

Як у PHP перейти від масиву, що містить дочірні => батьківські пари, до набору вкладених <ul>s?

У мене є відчуття, що рекурсія задіяна, але я не досить прокинувся, щоб продумати це.

Відповіді:


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
Тату. Як я міг змінити функцію printTree так, щоб вона не відповідала безпосередньо HTML-коду дерева, але зберегла весь вихідний HTML у змінну і повернула його? дякую
Енріке

Привіт, я думаю, що декларація функції повинна бути parseAndPrintTree ($ tree, $ root = null), а рекурсивний виклик повинен бути parseAndPrintTree ($ child, $ tree); З найкращими побажаннями
razor7

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 більш логічно? Дозволю собі не погодитися. У рекурсії немає нічого логічного; Є 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 щодо виведення масиву. Дилема полягає в тому, що кожен елемент не знає, чи слідкуватимуть діти чи ні, скільки попередніх елементів потрібно закрити. В іншій відповіді я вже вирішив це, використовуючи RecursiveIteratorIteratorі шукаючи getDepth()та іншу метаінформацію, яку написав мій власний текст Iterator: Введення вкладеної набірної моделі в<ul> підпорядковані, але приховані, "закриті" . Ця відповідь також показує, що з ітераторами ви досить гнучкі.

Однак це був попередньо відсортований список, тому він не був би придатний для вашого прикладу. Крім того, я завжди хотів вирішити це для якоїсь стандартної структури дерева, HTML <ul>та <li>елементів.

Основна концепція, яку я придумав:

  1. TreeNode- Абстрагує кожен елемент на простий TreeNodeтип, який може надати його значення (наприклад Name) та не має він дітей.
  2. TreeNodesIterator- A, RecursiveIteratorякий здатний повторювати набір (масив) цих TreeNodes. Це досить просто, оскільки TreeNodeтип уже знає, чи є у нього діти, а які -.
  3. RecursiveListIterator- RecursiveIteratorIteratorщо має всі необхідні події, коли він рекурсивно перебирає будь-який вид 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);

Це 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і забезпечує ітерацію всіх подій, які можуть статися. Перемикач / корпус всередині циклу передпліччя може потім вирішувати події.

Пов'язані:


3
Настільки ж "добре розроблений", як це рішення - наскільки саме це "спрощений спосіб", ніж попередні приклади - Це просто здається надмірно розробленим рішенням тієї самої проблеми
Андре

@Andre: За ступенем інкапсуляції IIRC. В іншій відповідній відповіді у мене є повністю неінкапсульований фрагмент коду, який набагато менший і тому може бути "більш спрощеним" залежно від POV.
хакре

@hakre Як я можу змінити клас "ListDecorator", щоб додати "id" до LI, який отримується з масиву дерев?
Гангеш

1
@Gangesh: Найпростіше з вістором вузла. ^^ Жарт трохи, прямо вперед - це розширити декоратор і редагувати beginElement (), отримати внутрішній ітератор (див. Для прикладу метод inset ()) та роботу з атрибутом id.
хакре

@hakre Спасибі Я спробую це.
Гангеш

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

Поки Олександр-Константинов спочатку може здатися непростим для читання, але воно є геніальним та експоненціально кращим з точки зору продуктивності, але це повинно було бути визнано найкращою відповіддю.

Дякую, товариш, я зробив орієнтир на вашу честь порівняти 2 рішення, представлені в цій публікації.

У мене було плоське дерево @ 250k з 6 рівнями, які мені довелося перетворити, і я шукав кращий спосіб зробити це і уникнути рекурсивних ітерацій.

Рекурсія проти довідки:

// 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 ($ дерево);

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_idПК (дитина) і самовідсилання loca_parent_id(батьків).

Метою є представлення цієї структури в HTML. Простий сервер запитів для повернення даних couyrse - це певний порядок, але я виявив недостатньо добре, щоб відобразити такі дані природним чином. Мені дуже хотілося обробляти прогулянку по дереву Oracle, LEVELщоб допомогти в дисплеї.

Я вирішив використовувати ідею "шляху", щоб однозначно визначити кожен запис. Наприклад:

Сортування масиву за маршрутом повинно полегшити обробку для змістовного відображення.

Я усвідомлюю, що використання асоціативних масивів і сортів обманює, оскільки це приховує рекурсивну складність операцій, але мені це виглядає простіше:

<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. ця таблиця містить чотири колонки.id - це завдання завдання, а ім'я - ім'я завдання.

-
-- 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 (), який викликає рекурсивний, якщо поточний ідентифікатор завдання перевищує попередній ідентифікатор завдання.

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 */ }

Детальніше

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.