Структура дерева из списка смежности
Я пытаюсь создать иерархический объект дерева из плоского массива с родительскими идентификаторами.
// `parent` represents an ID and not the nesting level.
var flat = [
{ id: 1, name: "Business", parent: 0 },
{ id: 2, name: "Management", parent: 1 },
{ id: 3, name: "Leadership", parent: 2 },
{ id: 4, name: "Finance", parent: 1 },
{ id: 5, name: "Fiction", parent: 0 },
{ id: 6, name: "Accounting", parent: 1 },
{ id: 7, name: "Project Management", parent: 2 }
];
Конечный древовидный объект должен выглядеть следующим образом:
{
id: 1,
name: "Business",
children: [
{
id: 2,
name: "Management",
children: [
{ id: 3, name: "Leadership" },
{ id: 7, name: "Project Management" }
]
}
// [...]
]
}
// [...]
Вы можете увидеть мою текущую работу над этой скриптой, но она работает только на первых двух уровнях.
Я думал о сборе сирот (объектов из flat
без родителя в tree
), а затем снова перебирал их, чтобы увидеть, есть ли у них родительский. Но это может означать много циклов над деревом, особенно со многими категориями на нескольких уровнях.
Я уверен, что есть более элегантное решение.
Ответы
Ответ 1
Похоже, вы можете сделать это за 2 прохода независимо от глубины дерева:
var flat = [
{ id: 1, name: "Business", parent: 0 },
{ id: 2, name: "Management", parent: 1 },
{ id: 3, name: "Leadership", parent: 2 },
{ id: 4, name: "Finance", parent: 1 },
{ id: 5, name: "Fiction", parent: 0 },
{ id: 6, name: "Accounting", parent: 1 },
{ id: 7, name: "Project Management", parent: 2 }
];
var nodes = [];
var toplevelNodes = [];
var lookupList = {};
for (var i = 0; i < flat.length; i++) {
var n = {
id: flat[i].id,
name: flat[i].name,
parent_id: ((flat[i].parent == 0) ? null : flat[i].parent),
children: []
};
lookupList[n.id] = n;
nodes.push(n);
if (n.parent_id == null) {
toplevelNodes.push(n);
}
}
for (var i = 0; i < nodes.length; i++) {
var n = nodes[i];
if (!(n.parent_id == null)) {
lookupList[n.parent_id].children = lookupList[n.parent_id].children.concat([n]);
}
}
console.log(toplevelNodes);
Ответ 2
Update
Я не был доволен дополнительной сложностью в своем оригинальном решении. Я добавляю еще одну версию, которая уменьшает эту сложность. Ему удается создавать данные за один проход. Это также дает возможность реструктурировать записи в деревьях по-разному, нежели их оригинальный формат, если это необходимо. (По умолчанию он удаляет только parent
node.)
Обновленная версия
Доступно на JSFiddle.
var makeTree = (function() {
var defaultClone = function(record) {
var newRecord = JSON.parse(JSON.stringify(record));
delete newRecord.parent;
return newRecord;
};
return function(flat, clone) {
return flat.reduce(function(data, record) {
var oldRecord = data.catalog[record.id];
var newRecord = (clone || defaultClone)(record);
if (oldRecord && oldRecord.children) {
newRecord.children = oldRecord.children;
}
data.catalog[record.id] = newRecord;
if (record.parent) {
var parent = data.catalog[record.parent] =
(data.catalog[record.parent] || {id: record.parent});
(parent.children = parent.children || []).push(newRecord);
} else {
data.tree.push(newRecord);
}
return data;
}, {catalog: {}, tree: []}).tree;
}
}());
Обратите внимание, что это будет работать независимо от порядка плоского списка - родительские узлы не должны предшествовать их дочерним элементам - хотя здесь нет ничего для сортировки узлов.
Оригинальная версия
Мое решение (также на JSFiddle):
var makeTree = (function() {
var isArray = function(obj) {return Object.prototype.toString.call(obj) == "[object Array]"; };
var clone = function(obj) {return JSON.parse(JSON.stringify(obj));};
var buildTree = function(catalog, structure, start) {
return (structure[start] || []).map(function(id, index) {
var record = catalog[id];
var keys = structure[start][index];
var children = isArray(keys) ? keys.map(function(key) {
return buildTree(catalog, structure, key);
}) : buildTree(catalog, structure, keys);
if (children.length) {
record.children = children;
}
return record;
})
};
return function(flat) {
var catalog = flat.reduce(function(catalog, record) {
catalog[record.id] = clone(record);
delete(catalog[record.id].parent);
return catalog;
}, {});
var structure = flat.reduce(function(tree, record) {
(tree[record.parent] = tree[record.parent] || []).push(record.id);
return tree;
}, {});
return buildTree(catalog, structure, 0); // this might be oversimplified.
}
}());