我有一个表示树的平面数组,我想使用尾部递归来构建嵌套对象。
我有以下代码可以运行并生成所需的输出,但是我不确定这是否是尾递归的正确实现。
请咨询:)
const myArray = [
{ id: 'root' },
{ id: 0, parent: 'root' },
{ id: 1, parent: 'root' },
{ id: 2, parent: 0 },
{ id: 3, parent: 1 },
{ id: 4, parent: 2 },
{ id: 5, parent: 1 },
{ id: 6, parent: 4 },
{ id: 7, parent: 0 },
{ id: 8, parent: 0 },
];
function makeNestedTreeFromArray(array, id, children) {
if (children.length <= 0) {
return array.find(entry => entry.id === id);
}
return ({
...array.find(entry => entry.id === id),
children: children.map(child => makeNestedTreeFromArray(
array,
child.id,
array.filter(entry => entry.parent === child.id),
))
});
}
const myTree = makeNestedTreeFromArray(
myArray,
'root',
myArray.filter(entry => entry.parent === 'root'),
);
console.log(myTree);
答案 0 :(得分:1)
您的函数没有尾部调用,并且在所有情况下都不会,因为您多次调用了递归调用:请记住,尾部调用优化基本上意味着该函数已变成循环,...在这种情况下是不可能的。
也就是说,与其使用递归方式查找所有嵌套元素并遍历数组多次,不如使用id来映射Map,然后只需要迭代两次:一次构建Map,第二次将每个元素链接到其父元素。 here可以很好地实现这一点。
这将是一个尾声版本(尽管我在这里只是使用循环):
function listToTree([el, ...rest], parent = new Map, roots = []) {
if(el.parentID)
parent.get(el.parentID).children.push(el);
else roots.push(el);
parent.set(el.id, el);
el.children = [];
if(!rest.length) return roots;
return listToTree(rest, parent, roots); // A proper tail call: This can be turned into a loop
}
答案 1 :(得分:1)
“尾部调用”是对函数的调用,该函数作为另一个函数的最后一件事发生(特别是,任何返回值都转发给调用者)。
例如:
function foo() {
...
return bar("hi"); // a tail call to bar
}
尾部递归意味着它是对函数本身的尾部调用,即递归尾部调用:
function foo() {
...
return foo(); // a recursive tail call, or: tail recursion
}
这不适用于您的代码,因为您拥有
function makeNestedTreeFromArray(array, id, children) {
...
return ({
...
即您的函数将返回一个新对象,而不是另一个函数调用(更不用说对其自身的调用)的结果了。
答案 2 :(得分:1)
尾部递归的基本原理是返回具有更改参数的相同函数。这样可以在不增加堆栈大小的情况下,用函数的新调用替换最后一个堆栈条目。
以下方法使用TCO并返回函数调用,并使用标准退出条件从函数顶部的递归函数返回。
该算法仅访问每个项目,并构建具有多个根的树。最后,仅返回所需的根。这种方法适用于未排序的数据,因为对于每个节点,id
和parent
的信息都被使用,并且它们之间的关系得以保留。
function getTree(data, root, index = 0, tree = {}) {
var o = data[index];
if (!o) return tree[root];
Object.assign(tree[o.id] = tree[o.id] || {}, o);
tree[o.parent] = tree[o.parent] || {};
tree[o.parent].children = tree[o.parent].children || [];
tree[o.parent].children.push(tree[o.id]);
return getTree(data, root, index + 1, tree);
}
const
data = [{ id: 'root' }, { id: 0, parent: 'root' }, { id: 1, parent: 'root' }, { id: 2, parent: 0 }, { id: 3, parent: 1 }, { id: 4, parent: 2 }, { id: 5, parent: 1 }, { id: 6, parent: 4 }, { id: 7, parent: 0 }, { id: 8, parent: 0 }],
tree = getTree(data, 'root');
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
答案 3 :(得分:1)
您可以通过将对象中的项目(parent_id)分组一次,并在需要子代时对其进行检索,从而降低代码的时间复杂度,从而优化代码。而不是在每次递归中搜索(查找或过滤)该父对象。
var listTree = (array, parentId, searchObj)=>{
if(searchObj === undefined) {
// Create the searchObject only once. Reducing time complexity of the code
searchObj = {};
array.forEach(data => {
if(searchObj[data.parent]){
searchObj[data.parent].push(data)
}else {
searchObj[data.parent] = [data];
}
});
}
let children = searchObj[parentId];
// return empty array if no parent is retrieved.
return !children ? [] : children.map(single=>{
// Pass in the same searchObj so the the search filter is not repeated again
single.children = listTree(array, single.id, searchObj)
return single;
})
}
// Run the code
listTree(myArray, 'root');