За последние 24 часа нас посетили 22800 программистов и 1523 робота. Сейчас ищут 678 программистов ...

Nested Sets дерево - PHP

Тема в разделе "PHP для новичков", создана пользователем Error202, 22 июл 2017.

Метки:
  1. Error202

    Error202 Новичок

    С нами с:
    1 дек 2014
    Сообщения:
    18
    Симпатии:
    0
    Здравствуйте!
    Есть массив
    Код (Text):
    1. $a = [
    2.     [0] => [
    3.         'lft' => 1,
    4.         'rgt' => 6,
    5.         'depth' => 1,
    6.         'title' => 'cat1'
    7.     ],
    8.     [1] => [
    9.         'lft' => 2,
    10.         'rgt' => 3,
    11.         'depth' => 2,
    12.         'title' => 'cat1-2'
    13.     ],
    14.     [2] => [
    15.         'lft' => 4,
    16.         'rgt' => 5,
    17.         'depth' => 2,
    18.         'title' => 'cat1-3'
    19.     ],
    20. ];
    Хочу получить вложенные деревья в 'children', чтобы получилось так

    Код (Text):
    1. $a = [
    2.     [0] => [
    3.         'lft' => 1,
    4.         'rgt' => 6,
    5.         'depth' => 1,
    6.         'title' => 'cat1',
    7.         'children' => [
    8.             [0] => [
    9.                 'lft' => 2,
    10.                 'rgt' => 3,
    11.                 'depth' => 2,
    12.                 'title' => 'cat1-2'
    13.             ],
    14.             [1] => [
    15.                 'lft' => 4,
    16.                 'rgt' => 5,
    17.                 'depth' => 2,
    18.                 'title' => 'cat1-3'
    19.             ],
    20.         ],
    21.     ],
    22. ];
    Запутался в рекурсии и в индексах lft, rgt...
    Не подскажите, как примерно надо это обыграть?

    Есть рабочий код, но нужно избавиться от each... Не могу осмыслить

    Код (Text):
    1. function nestedArray(&$result, $prefix = '', $level = -1)
    2.     {
    3.         $new = [];
    4.         if (is_array($result)) {
    5.             while(list($n, $sub) = each($result)) {
    6.                 $new[] = $sub;
    7.                 $count = count($new);
    8.                 $new[$count - 1]['_tree_level'] = $level + 1;
    9.                 if ($sub['rgt'] - $sub['lft'] != 1) {
    10.                     $new[$count - 1]['children'] = nestedArray($result, $prefix, $new[$count - 1]['_tree_level']);
    11.                     $new[$count - 1]['expanded'] = true;
    12.                 }
    13.                 $next_id = key(next($result));
    14.                 if ($next_id && $result[$next_id]['depth'] != $sub['depth']) {
    15.                     return $new;
    16.                 }
    17.             }
    18.         }
    19.         return $new;
    20.     }
     
  2. Алекс8

    Алекс8 Активный пользователь

    С нами с:
    18 май 2017
    Сообщения:
    1.730
    Симпатии:
    359
    я немного поофтоплю.. пока еще смотрю Ваш код.. мне вот интересно что заставляет людей использовать ссылке.. вечно с ними какие то засады..
    --- Добавлено ---
    list интересная кстати штука.. недавно читал))
    Код (Text):
    1. Внимание
    2. В PHP 5 list() присваивает значения начиная с самого правого. В PHP 7 list() - с самого левого.
    --- Добавлено ---
    уровней может быть много? сколько может быть элементов первого уровня? где указаны в массивах их родительские категории? вижу глубину, но глубина это не родительская категория.. она показывает уровень, а не какому массиву принадлежит вложенный массив. .
     
  3. Error202

    Error202 Новичок

    С нами с:
    1 дек 2014
    Сообщения:
    18
    Симпатии:
    0
    Ссылка в данном случае нужна, т.к. во всей рекурсии прогулка идет по одному и тому же массиву со сменой указателя.

    Это Nested Sets, там родитель определяется по left-right

    Код не мой, но переделать для совместимости не могу... Тут с внутренними указателями в массиве организовано (each, next). foreach и while
    в рекурсии начинают перебирать массив сначала...
     
  4. Алекс8

    Алекс8 Активный пользователь

    С нами с:
    18 май 2017
    Сообщения:
    1.730
    Симпатии:
    359
    а если будет несколько верхних уровней? как тогда определять кто родитель?
     
  5. MouseZver

    MouseZver Суперстар

    С нами с:
    1 апр 2013
    Сообщения:
    7.763
    Симпатии:
    1.322
    Адрес:
    Лень
    что $result содержится? покажи
     
  6. Error202

    Error202 Новичок

    С нами с:
    1 дек 2014
    Сообщения:
    18
    Симпатии:
    0
    Да, ступил... $result - это массив из примера $a

    Верхний уровень только один и имеет left = 1
    Вот пример дерева
    [​IMG]
     
  7. Алекс8

    Алекс8 Активный пользователь

    С нами с:
    18 май 2017
    Сообщения:
    1.730
    Симпатии:
    359
    никогда не пользовался)) пошел читать))
     
  8. mkramer

    mkramer Суперстар
    Команда форума Модератор

    С нами с:
    20 июн 2012
    Сообщения:
    8.556
    Симпатии:
    1.754
    Там всё гораздо проще, когда есть depth, можно даже без рекурсии. Простой стек организовываете, спускаясь по уровням добавляете ссылку на children, поднимаясь - выталкиваете обратно. Его для этого видимо туда и вставляют, так-то он для nested Sets не нужен. Я правда именно вашу задачу никогда не решал, я так обычно вывожу nested sets напрямую.
     
  9. Error202

    Error202 Новичок

    С нами с:
    1 дек 2014
    Сообщения:
    18
    Симпатии:
    0
    Спасибо!

    Подобрал такое решение

    Код (Text):
    1. $stack = array();
    2. $arraySet = array();
    3.  
    4.  
    5. foreach( $a as $intKey=>$arrValues) {
    6.     $stackSize = count($stack);
    7.     while($stackSize > 0 && $stack[$stackSize-1]['rgt'] < $arrValues['lft']) {
    8.             array_pop($stack);
    9.             $stackSize--;
    10.         }
    11.  
    12.     $link =& $arraySet;
    13.     for($i=0;$i<$stackSize;$i++) {
    14.         $link =& $link[$stack[$i]['index']]["children"];
    15.     }
    16.     $tmp = array_push($link,  array ('item'=>$arrValues,'children'=>array()));
    17.     array_push($stack, array('index' => $tmp-1, 'rgt' => $arrValues['rgt']));
    18.  
    19. }
    20.  
    21. print_r($arraySet);
     
  10. Dimon2x

    Dimon2x Старожил

    С нами с:
    26 фев 2012
    Сообщения:
    2.202
    Симпатии:
    184
    Начал изучать по этой статье http://www.getinfo.ru/article610.html . Не понимаю, как из этого потом вывести дерево?

    Код (Text):
    1. -- phpMyAdmin SQL Dump
    2. -- version 4.7.7
    3. -- https://www.phpmyadmin.net/
    4. --
    5. -- Хост: 127.0.0.1:3306
    6. -- Время создания: Янв 19 2019 г., 19:21
    7. -- Версия сервера: 5.7.20
    8. -- Версия PHP: 7.2.0
    9.  
    10. SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
    11. SET AUTOCOMMIT = 0;
    12. START TRANSACTION;
    13. SET time_zone = "+00:00";
    14.  
    15.  
    16. /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
    17. /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
    18. /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
    19. /*!40101 SET NAMES utf8mb4 */;
    20.  
    21. --
    22. -- База данных: `nested_sets`
    23. --
    24.  
    25. -- --------------------------------------------------------
    26.  
    27. --
    28. -- Структура таблицы `my_tree`
    29. --
    30.  
    31. CREATE TABLE `my_tree` (
    32.   `id` int(10) NOT NULL,
    33.   `name` varchar(150) NOT NULL,
    34.   `left_key` int(10) NOT NULL DEFAULT '0',
    35.   `right_key` int(10) NOT NULL DEFAULT '0',
    36.   `level` int(10) NOT NULL DEFAULT '0'
    37. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    38.  
    39. --
    40. -- Дамп данных таблицы `my_tree`
    41. --
    42.  
    43. INSERT INTO `my_tree` (`id`, `name`, `left_key`, `right_key`, `level`) VALUES
    44. (1, '1', 1, 23, 1),
    45. (2, '2', 2, 9, 2),
    46. (3, '3', 10, 23, 2),
    47. (4, '4', 24, 31, 2);
    48.  
    49. --
    50. -- Индексы сохранённых таблиц
    51. --
    52.  
    53. --
    54. -- Индексы таблицы `my_tree`
    55. --
    56. ALTER TABLE `my_tree`
    57.   ADD PRIMARY KEY (`id`),
    58.   ADD KEY `left_key` (`left_key`,`right_key`,`level`);
    59.  
    60. --
    61. -- AUTO_INCREMENT для сохранённых таблиц
    62. --
    63.  
    64. --
    65. -- AUTO_INCREMENT для таблицы `my_tree`
    66. --
    67. ALTER TABLE `my_tree`
    68.   MODIFY `id` int(10) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5;
    69. COMMIT;
    70.  
    71. /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
    72. /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
    73. /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
    Делаю запрос

    Код (Text):
    1. SELECT id, name, level FROM my_tree ORDER BY left_key
    Выводит список

    Код (Text):
    1.  
    2. id    name    level
    3. 1    1    1  
    4. 2    2    2
    5. 3    3    2
    6. 4    4    2
     
  11. Dimon2x

    Dimon2x Старожил

    С нами с:
    26 фев 2012
    Сообщения:
    2.202
    Симпатии:
    184
    Вот так по интересней

    Код (Text):
    1. INSERT INTO `my_tree` (`id`, `name`, `left_key`, `right_key`, `level`) VALUES
    2. (1, 'Овощи', 2, 9, 2),
    3. (2, 'Картошка', 3, 4, 3),
    4. (3, 'Капуста', 5, 6, 3),
    5. (4, 'Буряк', 7, 8, 3),
    6. (5, 'Фрукты', 10, 17, 2),
    7. (6, 'Банан', 11, 12, 3),
    8. (7, 'Киви', 13, 14, 3),
    9. (8, 'Яблоко', 15, 16, 3),
    10. (9, 'Ягоды', 18, 21, 2),
    11. (10, 'Арбуз', 19, 20, 3),
    12. (11, 'Другое', 22, 23, 2),
    13. (12, 'Еда', 1, 24, 1);
     
  12. Алекс8

    Алекс8 Активный пользователь

    С нами с:
    18 май 2017
    Сообщения:
    1.730
    Симпатии:
    359
    я пару раз пытался сделать многоуровневые категории через нестеды)) потом каждый раз стирал все и делал через id pid и рекурсию))
     
  13. Dimon2x

    Dimon2x Старожил

    С нами с:
    26 фев 2012
    Сообщения:
    2.202
    Симпатии:
    184
    @Алекс8 многие почему то рекомендуют Nested Sets
    --- Добавлено ---
    Я понял, что выводится отсортированный список по полю level, больше пока ничего не понял
     
  14. mkramer

    mkramer Суперстар
    Команда форума Модератор

    С нами с:
    20 июн 2012
    Сообщения:
    8.556
    Симпатии:
    1.754
    Ну а подумать? Либо преобразовать в рекурсивный массив, либо вывести с помощью стеков. Вот, я когда-то изобретал:
    PHP:
    1. <?php
    2. $a = [
    3.     ["id" => 1, "name" => 1, "level" => 1],
    4.     ["id" => 2, "name" => 2, "level" => 2],
    5.     ["id" => 3, "name" => 3, "level" => 2],
    6.     ["id" => 4, "name" => 4, "level" => 3],
    7.      ["id" =>5, "name" => 5, "level" => 4],
    8.     ["id" => 6, "name" => 6, "level" => 2],
    9. ];
    10. $s = [$a[0]["level"]];
    11. echo "<ul>";
    12. foreach ($a as $ind => $ae) {
    13.     if ($ae["level"] > $s[count($s) - 1]) {
    14.         $s[] = $ae["level"];
    15.         echo "<ul>";
    16.     } elseif ($ae["level"] < $s[count($s) - 1]) {
    17.         while ($ae["level"] < $s[count($s) - 1]) {
    18.             echo "</li></ul></li>";
    19.             array_pop($s);
    20.         }
    21.     }
    22.     if ($ind > 0 && $ae["level"] === $a[$ind - 1]["level"]) {
    23.         echo "</li>";
    24.     }
    25.     echo "<li>$ae[name]";
    26. }
    27. while (count($s)) {
    28.    echo "</li></ul>";
    29.    array_pop($s);
    30. }
    Может можно и проще
    --- Добавлено ---
    По полю left

    Потому что всё дерево выбирается одним запросом, т.е. чтоб избежать необходимости запросов в рекурсивной функции. Ты же знаешь, что в цикле и в рекурсии запросы не стоит использовать?
     
    #14 mkramer, 19 янв 2019
    Последнее редактирование: 19 янв 2019
    Dimon2x нравится это.
  15. Dimon2x

    Dimon2x Старожил

    С нами с:
    26 фев 2012
    Сообщения:
    2.202
    Симпатии:
    184
    @mkramer а разве способ с pid не проще?
     
  16. mkramer

    mkramer Суперстар
    Команда форума Модератор

    С нами с:
    20 июн 2012
    Сообщения:
    8.556
    Симпатии:
    1.754
    Проще, но менее эффективный, так как приходится делать запросы из рекурсивной функции. Здесь кода больше, но для больших деревьев потенциально будет выигрыш по времени из-за сокращения числа запросов
    --- Добавлено ---
    Этот код же тоже не шибко сложный, на самом деле, и отработает очень быстро
    --- Добавлено ---
    Т.е. ещё раз - зачем придумали nested sets - чтобы читать всё дерево одним запросом, со всеми листьями. C pid тоже есть метод, если ты хранишь порядок, т.е. если можешь одним запросом получить всё дерево, у которого потомки будут гарантированно идти после предков.
    --- Добавлено ---
    @Dimon2x, почитай что-нибудь не про PHP, а про алгоритмы. Дональда Кнута вряд ли потянешь (я и сам не потянул в своё время, к сожалению), а вот Никласа Вирта "Алгоритмы + структуры данных = программы" стоит почитать. Там, конечно, ещё не ООП, но ООП - это тоже в конечном итоге алгоритм, поэтому всё равно не помешает.
    --- Добавлено ---
    Я смог дойти до этого алгоритма сам, поскольку знал, что такое стек, что такое очередь, чем отличаются, как организовать. Работал с бинарными деревьями в ВУЗе (хоть его и не закончил), в нужный момент такие знания выручают.
     
    Dimon2x и Алекс8 нравится это.
  17. Алекс8

    Алекс8 Активный пользователь

    С нами с:
    18 май 2017
    Сообщения:
    1.730
    Симпатии:
    359
    физмат?)
     
  18. Dimon2x

    Dimon2x Старожил

    С нами с:
    26 фев 2012
    Сообщения:
    2.202
    Симпатии:
    184
    @mkramer Никлас Вирт там какой язык используется?
     
  19. MouseZver

    MouseZver Суперстар

    С нами с:
    1 апр 2013
    Сообщения:
    7.763
    Симпатии:
    1.322
    Адрес:
    Лень
    может фантик оборачивает саму конфету ?
    --- Добавлено ---
    PHP:
    1. foreach ( $a AS [ 'name' => $name, 'level' => $lvl ] )
    2. {
    3.     echo str_repeat ( '-', $lvl ) . "  $lvl" . PHP_EOL;
    4. }
    --- Добавлено ---
    или функция сама себя просит, уменьшая лвл
     
  20. mkramer

    mkramer Суперстар
    Команда форума Модератор

    С нами с:
    20 июн 2012
    Сообщения:
    8.556
    Симпатии:
    1.754
    @MouseZver Ну обычно задача отобразить дерево в виде древовидного html, как вложенные <ul> в моём примере, а если достаточно вывести чёрточки - то да, можно и как ты :) Если можешь предложить более элегантное решение, дающее тот же результат, что приведённый мной код, буду только рад почитать, я не претендую на лучшее из возможных решений. И, если я при воспроизведении не ошибся (проверял только для тех данных, что в коде, сейчас), правильно обрабатывает скачок уровней, когда, к примеру, после 4 уровня идёт снова 1-й
    Pascal или Modula 2, в зависимости от редакции, которые он же и изобрёл. PHP тогда даже в проекте не было, когда он эти книги писал :) Но эти языки легко понимать. там всё - английские слова, начало блока begin, конец - end, и в принципе, не в языке дела, а в объяснении логики алгоритмов и логики работы со структурами данных. Не обязательно даже запускать там примеры, можно просто разобраться в сути происходящего. Есть Дональд Кнут, там вообще без кода, но там реально очень сложная книга, хотя и даёт +500000 к скиллу программиста, если осилить. Повторюсь, я не осилил в своё время. Может быть всё-таки найду время, хотя сейчас просто на другое внимание уходит, не до возврата к основам.
    Не, это для меня слишком. Политех, тех. кибернетика. Углубляться в биографию, почему не закончил, не буду.
     
  21. MouseZver

    MouseZver Суперстар

    С нами с:
    1 апр 2013
    Сообщения:
    7.763
    Симпатии:
    1.322
    Адрес:
    Лень
    @mkramer сделал :)
    HTML:
    1. l=0
    2. l=0 < lvl=1
    3.    <ul><li>
    4.     1
    5.     l=1 < lvl=2
    6.        <ul><li>
    7.         2
    8.         l=2 === lvl=2
    9.         </li><li>
    10.         3
    11.         l=2 < lvl=3
    12.            <ul><li>
    13.             4
    14.             l=3 < lvl=4
    15.                <ul><li>
    16.                 5
    17.                 l=4 > lvl=2
    18.                 { </li></ul>
    19.             </li></ul>
    20.         <li> }
    21.         6
    22.  
    23. register_shutdown
    24. {
    25.     $l=2
    26.     (</li></ul>)
    27.     (</li></ul>)
    28. }
    PHP:
    1. <?php
    2. $a = [
    3.     ["id" => 1, "name" => 1, "level" => 1],
    4.     ["id" => 2, "name" => 2, "level" => 2],
    5.     ["id" => 3, "name" => 3, "level" => 2],
    6.     ["id" => 4, "name" => 4, "level" => 3],
    7.      ["id" =>5, "name" => 5, "level" => 4],
    8.     ["id" => 6, "name" => 6, "level" => 2],
    9.     ["id" => 7, "name" => 7, "level" => 7],
    10.     ["id" => 8, "name" => 8, "level" => 2],
    11. ];
    12.  
    13. function d ( array $arr )
    14. {
    15.     $l = 0;
    16.  
    17.     foreach ( $arr AS [ 'name' => $name, 'level' => $lvl ] )
    18.     {
    19.         if ( $l < $lvl )
    20.         {
    21.             echo str_repeat ( '<ul><li>', $lvl - $l ) . $name;
    22.          
    23.             $l = $lvl;
    24.         }
    25.         elseif ( $l > $lvl )
    26.         {
    27.             echo str_repeat ( '</li></ul>', $l - $lvl ) . '<li>' .$name;
    28.          
    29.             $l = $lvl;
    30.         }
    31.         else
    32.         {
    33.             echo '</li><li>' . $name;
    34.         }
    35.     }
    36.  
    37.     echo str_repeat ( '</li></ul>', $l );
    38. }
    39.  
    40. echo d ( $a ) . PHP_EOL;
    P.s: 7 слишком далеко кажется ? считаем с конца.
     
    #21 MouseZver, 20 янв 2019
    Последнее редактирование: 20 янв 2019
  22. MouseZver

    MouseZver Суперстар

    С нами с:
    1 апр 2013
    Сообщения:
    7.763
    Симпатии:
    1.322
    Адрес:
    Лень
    Колледж ВГППК - IT. Воронеж
    Информатика 3, не потому что я ее не понимал, а бунтовал из - за направлении "помощь вбития артикулов книжек для библиотеки". Место этого я срал на все мнения и угрозы, делал свой самый первый проект ICQ конструктор аськи изучая php. (2011)

    После я действительно понимал что некоторые предметы мне в жизни не впились. После обеда ВСЕГДА сбегал. Физ-ра 0 походов - она мне вообще не интересна. ( не потому что я дрыщь - у меня бывшая работа с керамической плиткой была, файлы с наклееной плиткой ( фулловые в обеих сторонах) по 100кг поднимаю в одно рыло. ).

    Практика IT всегда 5 - приходили в 9 часов и в 2 Уё. Рисовали в CoreDraw, фотошоп, анимацию, Exel World (BD) всякая дичь, которую за 2 часа сделаю - требую добавки (дальше по программе). Если нет, начинаю преподавалку молодую у нас троллить - у каждого компа установлен был агент наблюдения. Я его нахрен крашил батником, т.к диспетчер был недоступен для нас смертных. И занимался своими делами. Уйти пораньше нельзя было.

    После НГ я жестко заболел и практику всю пропустил. Зачеты небыли закрыты.

    В Феврале 2014 помоему, с довольным лицом иду в зав. отделение. "Всем адиос".
    --- Добавлено ---
    Больше всего запоминалось в чушке - УВК. Cyка да эти клоуны вообще хотят слушать мнения каждого ? Каждая ЧСВшная препод-баба, пыталась набить себе репутацию перед начальством, одна даже кривлялась рыжая бестия ( физика ).

    Старшекурсницы выбегая оттуда, рыдали. О боже "исключат" - великая потеря в Рашке. Все равно пойдешь в пятерочку.
    --- Добавлено ---
    если вобьете первую строчку в поисковик, уведите картинку где по середине старпер держит красную Т.Бумагу - Халерик (помидорный) с Завышенной ( до конечного края космоса ) ЧСВ, преподки из за него тоже рыдали. Всего лишь басс в голосе, а так тряпка.

    Почему дальше не пошел учиться ? А вот тут потребности в деньгах пошли. Можно было учиться и работать - (ага удачи). Можно работать и подрабатывать.
    --- Добавлено ---
    У меня до часу фантазии с интересной логикой были, забил до утра.
     
    #22 MouseZver, 20 янв 2019
    Последнее редактирование: 20 янв 2019
  23. mkramer

    mkramer Суперстар
    Команда форума Модератор

    С нами с:
    20 июн 2012
    Сообщения:
    8.556
    Симпатии:
    1.754
    Красиво, признаю :)
     
    MouseZver нравится это.
  24. MouseZver

    MouseZver Суперстар

    С нами с:
    1 апр 2013
    Сообщения:
    7.763
    Симпатии:
    1.322
    Адрес:
    Лень
    Посмотрел ютюб чушка своего, моя группа не обнаружена не единый человек. Нету выпуска, ничего, хоть и были там "умники" 2.
    2018 IT отдел Delete.
     
  25. Dimon2x

    Dimon2x Старожил

    С нами с:
    26 фев 2012
    Сообщения:
    2.202
    Симпатии:
    184
    @MouseZver а тебе сколько лет?