Конвертирование массива с деревом

Николай В.
На сайте с 07.09.2006
Offline
62
1101

Что-то я застрял со следующей задачей :gm: :

Есть результат запроса NestedSets-дерева отсортированный (как надо) по левому ключу:


Array (
[0] => Array (
[name] => Д'Артаньян
[level] => 1
)
[1] => Array (
[name] => Сын Д'Артаньяна
[level] => 2
)
[2] => Array (
[name] => Атос
[level] => 1
)
[3] => Array (
[name] => Портос
[level] => 1
)
[4] => Array (
[name] => Сын Портоса
[level] => 2
)
[5] => Array (
[name] => Внук Портоса
[level] => 3
)
[6] => Array (
[name] => Еще один внук Портоса
[level] => 3
)
[7] => Array (
[name] => Дочь Портоса
[level] => 2
)
[8] => Array (
[name] => Арамис
[level] => 1
)
[9] => Array (
[name] => Сын Арамиса
[level] => 2
)
)

Требуется сконвертировать это в массив со вложенными массивами веток, т.е.


Array (
[0] => Array (
[name] => Д'Артаньян
[tree] => Array (
[0] => Array (
[name] => Сын Д'Артаньяна
[tree] => Array ()
)
)
)
[1] => Array (
[name] => Атос
[tree] => Array ()
)

[2] => Array (
[name] => Портос
[tree] => Array (
[0] => Array (
[name] => Сын Портоса
[tree] => Array (
[0] => Array (
[name] => Внук Портоса
[tree] => Array ()
)
[1] => Array (
[name] => Еще один внук Портоса
[tree] => Array ()
)
)
)
[1] => Array (
[name] => Дочь Портоса
[tree] => Array ()
)
)
)

[3] => Array (
[name] => Арамис
[tree] => Array (
[0] => Array (
[name] => Сын Арамиса
[tree] => Array ()
)
)
)

)

Помогите, пожалуйста, с алгоритмом. :gm:

Mmonger
На сайте с 01.12.2005
Offline
165
#1

У вас в массиве у элемента нет идентификатора родительского элемента. Всегда дочерние элементы следуют строго за родительским, причём в порядке возрастания вложенности? Если да, алогоритм напишу, если нет - алгоритм невозможен в принципе.

Всё будет хорошо, но мы приложим усилия!
K
На сайте с 14.08.2006
Offline
56
ksm
#2

кстати хотел заметить, что в принципе достаточно такой структуры:

name, parent_id:


Array (
[0] => Array (
[name] => Д'Артаньян
[parent_id] => -1
)
[1] => Array (
[name] => Сын Д'Артаньяна
[parent_id] => 0
)
[2] => Array (
[name] => Атос
[parent_id] => -1
)
....

если parent_id == -1 то это корень, другое значение ссылается на родителя.

QAвед-sunтехник
pinalex
На сайте с 22.10.2006
Offline
8
#3
ksm:
кстати хотел заметить

Вряд ли это кстати. Топикстартер говорит о дереве NestedSets.

Николай В., левого ключа и уровня для такой сортировки недостаточно. Правый ключ также нужен.

Sadie
На сайте с 11.04.2005
Offline
64
#4

А, соббсно, в чем проблема? Или я не могу понять непостижимой глубины поставленной задачи? 😮

Имхо, это классическая задача на перевод бинарного дерева в обычное (обратите внимание на то, как перечислены субъекты в списке)

Подробнее:

выглядит примерно вот так:

Заводим элемент с нулевым левелом
Проходим по исходному списку.
Если левел следующего элемента списка выше текущего на единицу, добавляем его в качестве сына.
Если левел следующего элемента списка выше текущего на два, вызываем себя рекурсивно, задавая в качестве параметров последнего добавленного сына и продолжение списка.
Если левел следующего элемента списка равен обрабатываемому "мушкетеру" - выходим.
Если список кончился - работа выполнена.
Новости без комплексов (http://www.kompleksov.net/) | ЖЖ (http://sad-sadie.livejournal.com/)
bondarev.pp.ru
На сайте с 29.09.2005
Offline
202
#5

Да, задача не тривиальная :) Тоже пришлось побиться минут 15.

Самый логичный, казалось бы путь - пройтись по линейному списку и дать каждому элементу parent_id, а зетем уже конвертировать в дерево с момощью рекурсии.

Но есть более быстродействиенный метод:

$array = array ();
$way = array();
$lastlevel = 1;
$cnt = 0;
foreach ($list as $id=>$item) {
if ($item['level'] > $lastlevel) {
$way[] = $id;
} else if ($item['level'] == $lastlevel) {
if ($cnt > 0) {
array_pop($way);
}
$way[] = $id;
} else {
array_pop($way);
array_pop($way);
$way[] = $id;
}
$lastlevel = $item['level'];
$str = '$array';
$wcnt = 0;
foreach ($way as $id) {
$str .= (($wcnt > 0)?'[\'tree\']':'') . "[{$id}]";
$wcnt ++;
}
$str .= ' = $item;';
eval($str);
$cnt ++;
}

Тут приходится прибегать к функции eval() - это сильно усложняет чтение кода и портит его логику. Программисты не любят эту функцию. Но использовать здесь рекурсию, имхо, еще хуже :)

В общем, идея такая: мы пробегаемся по списку и для каждого элемента генерим строку вроде

$array[3]['tree'][4]['tree'][5] = $item;

а затем заставляем интерпретатор отработать эту строку с помощью eval().

В массиве $way мы храним номера родительских элементов. То есть 3, 4, 5. Если level текущего элемента больше level прошлого элемента, добавляем к этому массиву номер текущего элемента. Если они равны - убираем один и заменяем его новым. Если level текущего элемента меньше, чем level прошлого, убираем два номера и добавляем текущий.

Скрипт работает. Проверил.

Только для простоты ключами многоуровневого массива сделал id элементов. Но это вам, я думаю, несущественно.

bondarevpipes.com (http://ru.bondarevpipes.com/)
bondarev.pp.ru
На сайте с 29.09.2005
Offline
202
#6

Исходный массив $list

Получаем $array

dkameleon
На сайте с 09.12.2005
Offline
386
#7
bondarev.pp.ru:
зетем уже конвертировать в дерево с момощью рекурсии.

Можно сразу в дерево, не назначая предка:


function Nested2Tree(&$nested, $level) {
$result = array();
$item = array_shift($nested);
while(($item["level"] >= $level) && (count($nested) > 0)) {
$result[] = array(
"name" => $item["name"],
"tree" => Nested2Tree($nested, $level + 1),
);
$item = array_shift($nested);
}
if ($item["level"] < $level) { array_unshift($nested, $item); }
return $result;
}

$tree = Nested2Tree($nested, 1);
Дизайн интерьера (http://balabukha.com/)
Николай В.
На сайте с 07.09.2006
Offline
62
#8

dkameleon, благодарю. То что надо.

Господа и дамы, спасибо огромное всем за помощь.

Авторизуйтесь или зарегистрируйтесь, чтобы оставить комментарий