更新 2,(六年后)
我碰巧又一次偶然发现了这一点,并意识到这可以通过一点 ES6 解构和简单的递归来简单得多:
const makeForest = (id, xs) =>
xs .filter (({parent}) => parent == id)
.map (({id, parent, ...rest}) => ({id, ...rest, children: makeForest (id, xs)}))
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}];
console .log (
makeForest (0, flat)
)
.as-console-wrapper {min-height: 100% !important; top: 0}
注意名称更改。我们不是在制作一棵树,而是在整个森林中,根的每个直接子节点都有自己的节点。如果您有一个用于根的结构,那么将其更改为生成一棵树将是相对微不足道的。另请注意,这将使我们找到以 any id 为根的树。它不必是整个根。
性能
正如您所担心的那样,这确实具有O(n^2) 的最坏情况性能。实际运行时间类似于O(n * p),其中 p 是列表中不同父节点的数量。因此,只有当我们的树几乎与列表的长度一样深时,我们才接近最坏的情况。我会使用这样的东西,除非并且直到我可以证明它是我代码中的热点。我的猜测是我永远不会发现需要更换它。
课程
永远不要排除递归。这比这里的任何其他答案都简单得多,包括我下面的两个版本。
更新 1
我对原始解决方案中的额外复杂性不满意。我正在添加另一个版本来降低这种复杂性。它设法一次性构建数据。如有必要,它还使人们有机会以不同于原始格式的方式重组树中的记录。 (默认情况下,它只删除parent 节点。)
更新版本
可通过 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.
}
}());