从具有父字段的平面列表构造层次结构树? [复制]
Posted
技术标签:
【中文标题】从具有父字段的平面列表构造层次结构树? [复制]【英文标题】:Construct hierarchy tree from flat list with parent field? [duplicate] 【发布时间】:2014-03-13 02:06:33 【问题描述】:我有一个带有parent
字段的“页面”对象列表。此父字段引用列表中的另一个对象。我想根据这个字段从这个列表中创建一个树层次结构。
这是我的原始列表的样子:
[
id: 1,
title: 'home',
parent: null
,
id: 2,
title: 'about',
parent: null
,
id: 3,
title: 'team',
parent: 2
,
id: 4,
title: 'company',
parent: 2
]
我想把它转换成这样的树形结构:
[
id: 1,
title: 'home',
parent: null
,
id: 2,
title: 'about',
parent: null,
children: [
id: 3,
title: 'team',
parent: 2
,
id: 4,
title: 'company',
parent: 2
]
]
我希望有一个可重用的函数,我可以随时针对任意列表调用它。有人知道处理这个问题的好方法吗?任何帮助或建议将不胜感激!
【问题讨论】:
【参考方案1】:function treeify(list, idAttr, parentAttr, childrenAttr)
if (!idAttr) idAttr = 'id';
if (!parentAttr) parentAttr = 'parent';
if (!childrenAttr) childrenAttr = 'children';
var treeList = [];
var lookup = ;
list.forEach(function(obj)
lookup[obj[idAttr]] = obj;
obj[childrenAttr] = [];
);
list.forEach(function(obj)
if (obj[parentAttr] != null)
if (lookup[obj[parentAttr]] !== undefined)
lookup[obj[parentAttr]][childrenAttr].push(obj);
else
//console.log('Missing Parent Data: ' + obj[parentAttr]);
treeList.push(obj);
else
treeList.push(obj);
);
return treeList;
;
Fiddle
【讨论】:
请记住,上面的答案使用了两个循环,因此可以改进。由于我找不到实现 O(n) 解决方案的 npm 模块,因此我创建了以下模块(单元测试,100% 代码覆盖率,大小只有 0.5 kb 并且包括类型。也许它可以帮助某人:npmjs.com/package /performant-array-to-tree @PhilipStanislaus:O(2n) = O(n)
。我的解决方案也是O(n)
。我承认我没有达到速度记录。可以测量我的版本还是您的版本更快(两个简单循环与一个复杂循环),但我无法先验地判断哪个更快。但是,它们的时间复杂度完全相同。
@Amadan 感谢您的想法。你是对的,他们的时间复杂度是相等的。我发布了链接,以防有人对 npm 包中的高效实现感兴趣。
因为我的 npm 包performant-array-to-tree
收到了很多下载,所以我在 npm 上编写了一些可用包的基准测试。请在此处找到结果:github.com/philipstanislaus/array-to-tree-benchmarks。如果您有兴趣,很高兴添加其他人!【参考方案2】:
接受的答案对我的研究非常有帮助,但是,我必须在心里解析 id 参数,我理解这会使函数更灵活,但对于算法新手来说可能有点难以推理。
如果其他人遇到这个困难,这里的代码基本相同,但可能更容易理解:
const treeify = (arr, pid) =>
const tree = [];
const lookup = ;
// Initialize lookup table with each array item's id as key and
// its children initialized to an empty array
arr.forEach((o) =>
lookup[o.id] = o;
lookup[o.id].children = [];
);
arr.forEach((o) =>
// If the item has a parent we do following:
// 1. access it in constant time now that we have a lookup table
// 2. since children is preconfigured, we simply push the item
if (o.parent !== null)
lookup[o.parent].children.push(o);
else
// no o.parent so this is a "root at the top level of our tree
tree.push(o);
);
return tree;
;
它与一些 cmets 接受的答案相同,用于解释发生了什么。这是一个用例,它会根据级别生成一个 div 列表,其中包含内联 marginLeft
缩进的页面:
const arr = [
id: 1, title: 'All', parent: null,
id: 2, title: 'Products', parent: 1,
id: 3, title: 'Photoshop', parent: 2,
id: 4, title: 'Illustrator', parent: 2,
id: 4, title: 'Plugins', parent: 3,
id: 5, title: 'Services', parent: 1,
id: 6, title: 'Branding', parent: 5,
id: 7, title: 'Websites', parent: 5,
id: 8, title: 'Pen Testing', parent: 7];
const render = (item, parent, level) =>
const div = document.createElement('div');
div.textContent = item.title;
div.style.marginLeft = level * 8 + 'px';
parent.appendChild(div);
if (item.children.length)
item.children.forEach(child => render(child, div, ++level));
return parent;
const fragment = document.createDocumentFragment();
treeify(arr)
.map(item => render(item, fragment, 1))
.map(frag => document.body.appendChild(frag))
如果你想运行 Codepen:https://codepen.io/roblevin/pen/gVRowd?editors=0010
在我看来,这个解决方案的有趣之处在于,查找表使用项目的 ID 作为键保持平坦,并且只有根对象被推入结果树列表。然而,由于 javascript 对象的引用性质,根有它的孩子,孩子有他们的孩子,等等,但它本质上是从根连接起来的,因此是树状的。
【讨论】:
以上是关于从具有父字段的平面列表构造层次结构树? [复制]的主要内容,如果未能解决你的问题,请参考以下文章
XCUITest - 选定的单元格未显示在 iOS 15 的层次结构树中
org-mode,如何使用格式良好的组织链接自动生成漂亮的文件层次结构树