简体   繁体   English

使用尾部递归实现javascript函数

[英]Implement javascript function using tail recursion

I have got a flat array representing a tree, and I want to build a nested object using tail recursion. 我有一个表示一棵树的平面数组,我想使用尾部递归构建一个嵌套的对象。

I've got the following code to run and generate the desired output, but I am not sure if it is a proper implementation of tail recursion. 我有以下代码可以运行并生成所需的输出,但是我不确定这是否是尾递归的正确实现。

Please advice :) 请指教 :)

 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); 

Your function does not have a tail call and it won't under all circumstances, because you call the recursive call more than once: Remember that tail call optimization basically means that the function is turned into a loop, ... which is impossible for this case. 您的函数没有尾部调用,并且在所有情况下都不会,因为您多次调用了递归调用:请记住,尾部调用优化基本上意味着函数变成了循环,...对于这个案例。

That said, instead of finding all the nested elements recursively and iterating over the array a lot of times, use a id to object Map, then you just need to iterate twice: Once to build up the Map, and a second time to link each element to it's parent. 也就是说,与其递归地查找所有嵌套元素并多次遍历数组,而使用id来反对Map,则只需要迭代两次:一次构建Map,第二次链接每个它的父元素。 A great implementation of that can be found here . 可以在这里找到一个很好的实现。

Here would be a tail-call version (I would just use a loop here though): 这将是一个尾声版本(尽管我只是在这里使用循环):

 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
 }

A "tail call" is a call to a function that occurs as the last thing in another function (in particular, any return values are forwarded to the caller). “尾部调用”是对函数的调用,该函数在另一个函数中作为最后一个事物出现(特别是,所有返回值都转发给调用者)。

For example: 例如:

function foo() {
    ...
    return bar("hi");  // a tail call to bar
}

Tail recursion means it's a tail call to the function itself, ie a recursive tail call: 尾递归意味着它是对函数本身的尾调用,即递归尾调用:

function foo() {
    ...
    return foo();  // a recursive tail call, or: tail recursion
}

This does not apply to your code because you have 这不适用于您的代码,因为您有

function makeNestedTreeFromArray(array, id, children) {
  ...
  return ({
    ...

Ie your function returns a new object, not the result of another function call (let alone a call to itself). 即您的函数返回一个新对象,而不是另一个函数调用(更不用说对自身的调用)的结果了。

The basics of a tail recursion is to return the same function with changed parameters. 尾递归的基础是返回具有更改参数的相同函数。 This allows to replace the last stack entry with the new call of the function without increasing the stack size. 这允许用函数的新调用替换最后一个堆栈条目,而无需增加堆栈大小。

The following approach uses a TCO and returns the function call and uses a standard exit condition to return from a recursive function at the top of the function. 以下方法使用TCO并返回函数调用,并使用标准退出条件从函数顶部的递归函数返回。

The algorithm visits each item only ones and builds a tree which has multiple roots. 该算法仅访问每个项,并构建具有多个根的树。 At the end only the wanted root is returned. 最后,仅返回所需的根。 This approach works for unsorted data, because for every node, both information of id and parent are used and their relation is preserved. 这种方法适用于未排序的数据,因为对于每个节点,都使用idparent信息,并保留它们之间的关系。

 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; } 

You can optimize the code by reducing the time complexity of your code by grouping the items (parent_id) in an object just once and retrieving it whenever you need the children for that parent instead of searching (find or filter) through it in every recursion. 您可以通过将对象中的项目(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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM