[英]Implement javascript function using tail recursion
我有一個表示一棵樹的平面數組,我想使用尾部遞歸構建一個嵌套的對象。
我有以下代碼可以運行並生成所需的輸出,但是我不確定這是否是尾遞歸的正確實現。
請指教 :)
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);
您的函數沒有尾部調用,並且在所有情況下都不會,因為您多次調用了遞歸調用:請記住,尾部調用優化基本上意味着函數變成了循環,...對於這個案例。
也就是說,與其遞歸地查找所有嵌套元素並多次遍歷數組,而使用id來反對Map,則只需要迭代兩次:一次構建Map,第二次鏈接每個它的父元素。 可以在這里找到一個很好的實現。
這將是一個尾聲版本(盡管我只是在這里使用循環):
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
}
“尾部調用”是對函數的調用,該函數在另一個函數中作為最后一個事物出現(特別是,所有返回值都轉發給調用者)。
例如:
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 ({
...
即您的函數返回一個新對象,而不是另一個函數調用(更不用說對自身的調用)的結果了。
尾遞歸的基礎是返回具有更改參數的相同函數。 這允許用函數的新調用替換最后一個堆棧條目,而無需增加堆棧大小。
以下方法使用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; }
您可以通過將對象中的項目(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');
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.