简体   繁体   English

创建一个递归函数,该函数采用平面数组并转换为树数据结构

[英]Create a recursive function that takes a flat array and converts to a tree data structure

So I'm trying to write a recursive function that takes a flat array of objects with their value, id, and the id of their parent node and transform it to a tree structure, where the children of the structure are an array of nodes. 所以我正在尝试编写一个递归函数,它接受一个平面数组的对象及其值,id和父节点的id,并将其转换为树结构,其中结构的子节点是一个节点数组。 Children need to be sorted by id and if its null it can be the root node. 子项需要按id排序,如果它为null则它可以是根节点。

The function im trying to write function toTree(data), only should take in the data array. 我试图写函数toTree(数据)的函数,只应该在数据数组中。 I've been unable to do it without a parent. 没有父母,我一直无法做到。 What I have so far is a function(below) that takes data and parent to get started. 到目前为止我所拥有的是一个函数(下面),它使数据和父元素开始。

input: 输入:

 const tasks = [
  { id: 1, parent: null, value: 'Make breakfast' },
  { id: 2, parent: 1, value: 'Brew coffee' },
  { id: 3, parent: 2, value: 'Boil water' },
  { id: 4, parent: 2, value: 'Grind coffee beans' },
  { id: 5, parent: 2, value: 'Pour water over coffee grounds' }
];

output: 输出:

{
  id: 1,
  parent: null,
  value: 'Make Breakfast',
  children: [
     {
       id: 2,
       parent: 1,
       value: 'Brew coffee',
       children: [
          { id: 3, parent: 2, value: 'Boil water' },
          { id: 4, parent: 2, value: 'Grind coffee beans' },
          { id: 5, parent: 2, value: 'Pour water over coffee grounds'     }
       ]
     }
  ]
}


funciton toTree(data) {
  customtoTree (data, null);
}

function customToTree (data, parent) {
  const out = [];
  data.forEach((obj) => {
    if (obj.parent === parent) {
      const children = customToTree(data,obj.parent);

      if (children.length) {
        obj[children[0]] = children;
      }
      const {id,parent, ...content} = obj;
      out.push(content);
    }
  });
  return out;
}

I would really like to understand the correct logic on how to do this and think about this and how to do it without giving a parent explicitly. 我真的想了解如何做到这一点的正确逻辑,并思考这个以及如何在不明确给出父级的情况下做到这一点。

I couldn't check for more test cases but this is something I was quickly able to come up with which passes your use case, it looks not so good, I would recommend to use it as initial structure and then build on it. 我无法检查更多测试用例,但这是我很快能够提出的,它通过你的用例,它看起来不太好,我建议使用它作为初始结构,然后在它上面构建。 Also, I am assuming that tasks are sorted in ascending order by parent, ie child will only appear after its parent in tasks array 此外,我假设任务按父级的升序排序,即子级只会在其父级的tasks数组中出现

const tasks = [
  { id: 1, parent: null, value: 'Make breakfast' },
  { id: 2, parent: 1, value: 'Brew coffee' },
  { id: 3, parent: 2, value: 'Boil water' },
  { id: 4, parent: 2, value: 'Grind coffee beans' },
  { id: 5, parent: 2, value: 'Pour water over coffee grounds' },
  { id: 6, parent: 5, value: 'Pour water over coffee grounds' },
  { id: 7, parent: 5, value: 'Pour water over coffee grounds' }
];

function Tree() {
  this.root = null;
  // this function makes node root, if root is empty, otherwise delegate it to recursive function
  this.add = function(node) {
    if(this.root == null)
      this.root = new Node(node);
    else
      // lets start our processing by considering root as parent
      this.addChild(node, this.root);
  }

  this.addChild = function(node, parent) {
    // if the provided parent is actual parent, add the node to its children
    if(parent.id == node.parent) {
      parent.children[node.id] = new Node(node);
    } else if(parent.children[node.parent]) {
      // if the provided parent children contains actual parent call addChild with that node
      this.addChild(node, parent.children[node.parent])
    } else if(Object.keys(parent.children).length > 0) {
      // iterate over children and call addChild with each child to search for parent
      for(let p in parent.children) {
        this.addChild(node, parent.children[p]);
      }
    } else {
      console.log('parent was not found');
    }
  }
}

function Node (node) {
  this.id = node.id;
  this.parent = node.parent;
  this.value = node.value;
  this.children = {};
}

const tree = new Tree();

// We are assuming that tasks are sorted in ascending order by parent

for(let t of tasks) {
  tree.add(t);
}

console.log(JSON.stringify(tree.root))

Let me know if you have questions. 如果您有疑问,请告诉我。 Lets crack it together 让我们一起破解它

I had the same question during an interview, and I haven't been able to solve it. 我在面试时遇到了同样的问题,但我无法解决。 I was also confused that the function should only take the array as a first and only argument. 我也很困惑,该函数应该只将数组作为第一个也是唯一的参数。

But after reworking it later (and with some very good suggestions from a brilliant man), I realized that you can call the function with the array as the first and only argument the first time and then during the recursion call passing the parent as a second argument. 但是稍后重新编写它(以及一个聪明人的一些非常好的建议),我意识到你可以在第一次和第一次调用函数时将数组作为第一个也是唯一的参数,然后在递归调用期间将父函数作为第二个传递论点。

Inside the function, you only need to check if the second argument is undefined, if it is, you search in the array for your root object and assign it to your second argument. 在函数内部,您只需要检查第二个参数是否未定义,如果是,则在数组中搜索根对象并将其分配给第二个参数。

So here is my solution, I hope it will be clearer : 所以这是我的解决方案,我希望它会更清楚:

function toTree(arr, item) {

        if (!item) {
            item = arr.find(item => item.parent === null)
        }

        let parent = {...item}
        parent.children = 
            arr.filter(x => x.parent === item.id)
                .sort((a, b) => a.id - b.id)
                .map(y => toTree(arr, y))

        return parent     
}

toTree(tasks)

If your input is already sorted by id and no child node can come before its parent Node in the list, then you can do this in one loop and don't even need recursion: 如果您的输入已经按id排序,并且没有子节点可以在其列表中的父节点之前,那么您可以在一个循环中执行此操作,甚至不需要递归:

  const tasks = [ { id: 1, parent: null, value: 'Make breakfast' }, { id: 2, parent: 1, value: 'Brew coffee' }, { id: 3, parent: 2, value: 'Boil water' }, { id: 4, parent: 2, value: 'Grind coffee beans' }, { id: 5, parent: 2, value: 'Pour water over coffee grounds' } ]; const tasksById = Object.create(null); // abusing filter to do the work of a forEach() // while also filtering the tasks down to a list with `parent: null` const root = tasks.filter((value) => { const { id, parent } = value; tasksById[id] = value; if(parent == null) return true; (tasksById[parent].children || (tasksById[parent].children = [])).push(value); }); console.log("rootNodes", root); console.log("tasksById", tasksById); 
 .as-console-wrapper{top:0;max-height:100%!important} 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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