Javascript 在深度嵌套对象上递归迭代以查找父对象

Javascript 在深度嵌套对象上递归迭代以查找父对象,javascript,ecmascript-6,Javascript,Ecmascript 6,我有一个包含子项的数据树结构: { id: 1, name: "Dog", parent_id: null, children: [ { id: 2, name: "Food", parent_id: 1, children: [] }, { id: 3, name:

我有一个包含子项的数据树结构:

{  id: 1,
   name: "Dog",
   parent_id: null,
   children: [
         {
             id: 2,
             name: "Food",
             parent_id: 1,
             children: []
         },
         {
             id: 3,
             name: "Water",
             parent_id: 1,
             children: [
                 {
                    id: 4,
                    name: "Bowl",
                    parent_id: 3,
                    children: []
                 },
                 {
                    id: 5,
                    name: "Oxygen",
                    parent_id: 3,
                    children: []
                 },
                 {
                    id: 6,
                    name: "Hydrogen",
                    parent_id: 3,
                    children: []
                 }
             ]
         }
   ]
}
如上面的数据所示,任何子数据对象都可以有更多的子对象。这表示一个DOM结构,用户可以从中选择要向其中添加子项的项

我有来自DOM的选定项的已知文本标题以及用户想要插入的数据。我很难找到一个递归算法,它允许我将新数据添加到树的正确级别

下面是我思考问题并尝试对其进行伪代码的列表:

投入:

  • 树(上面的数据)
  • 来自DOM中单击项的parentTitle
  • 产出:

  • 插入项的树
  • 步骤:

  • 确定最高使用id以了解下一个唯一id是什么
  • 检查当前数据级别是否与父级标题匹配
  • 如果匹配,则在新数据中设置id和父\u id,并推入父项的子项
  • 如果不匹配,则检查当前级别数据是否有子级
  • 如果当前级别有子级,则需要对每个子级重复步骤2+,直到找到匹配项
  • 这是我的密码:

    function askUserForNewItem(e) {
       const tree = getTree(); // returns above tree data structure
       const name = prompt( 'Enter new item’s name:' ); // user input to match and insert as new item in tree
       const clickedTitle = getClickedTitle(e); // returns string title of clicked on item from DOM - for example "Dog" or "Bowl"
       const parent = determineParent(tree, clickedTitle);
       const parent_id = parent[0].id;
       // TODO - needs to set real unique id (highest unused id)
       const newId = 101; // hard coded for now, needs to be dynamic
       // TODO - needs to insert into correct level of children array in tree
       return tree.children.push({ id: newId, name, emoji, children: [], parent_id: parent_id });
    }
    
    function determineParent(tree, clickedTitle) {
    
        if(tree.children.length === 0) {
            return false;
        }
        let treeLevel = tree;
        let parent = [];
        while(treeLevel.children.length !== 0) {
            parent = treeLevel.children.filter(child => child.name === clickedTitle);
            if(parent.length !== 0) {
                break;
            }
            else {
                // what to do here to make this recursive?
            }
        }
    
        return parent;
    }
    
    因此,如果用户在单击“Dog”的添加按钮时键入“Cat”,则会创建一个新对象

    {
        id: 7,
        name: "Cat",
        parent_id: 1,
        children: []
    }
    

    将被插入到数据树中第一级“Dog”对象的子对象中。

    如果需要递归解决方案,应修改determineParent方法,使其在树下搜索。 我不确定这正是你要寻找的,但我希望你能大致了解

    function determineParent(curNode, clickedTitle) {
        if(curNode.name===clickedTitle)
            return curNode; // found the parent node with the correct clickedTitle
    
        // not found yet, do a recusive search down the tree.
    
        for(const node of curNode.children) {
            return determineParent(node,clickedTitle);
        }
    
        return null; // not found.
    }
    
    想法是从最顶端的节点(curNode)开始,首先确定它是否是正确的父节点,如果不是,则取第一个子节点,查看它是否匹配,如果不匹配,则向下搜索它的子节点,依此类推

    当处理递归时,可能需要处理可能经历循环引用的情况,考虑一个节点有一个指向节点父节点或祖父节点的子节点的情况,递归方法将永远运行(在实际中,它将耗尽堆栈空间并抛出异常)。 一种方法是,它包含一个在每次递归调用时减少的保护计数器,然后在它达到零时退出

    function determineParent(curNode, clickedTitle, safeGuard) {
        if(curNode.name===clickedTitle)
            return curNode; // found the parent node with the correct clickedTitle
    
        if(safeGuard===0)
            return null; // bail out 
    
        // not found yet, do a recusive search down the tree.
        for(const node of curNode.children) {
            return determineParent(node,clickedTitle,--safeGuard);
        }
    
        return null; // not found.
    }
    
    然后像这样称呼它

      this.determineParent(tree,"title",100);
    
    要将搜索次数限制为100。

    如果可以使用Lodash+,则:


    不太喜欢重新发明轮子,尤其是在算法方面。以下是您可以使用的解决问题的方法。我们使用它进行数据处理,因为一旦你把脑袋绕在它身上,它就非常强大了

    //const objectScan=require('object-scan');
    const insert=(树、父名、子名)=>objectScan(['**.name']{
    中止:对,
    rtn:‘bool’,
    filterFn:({value,parent})=>{
    如果(值===父名称){
    父、子、推({
    id:Math.max(…objectScan(['**.id']),{rtn:'value'}(树))+1,
    姓名:childName,
    parent_id:parent.id,
    儿童:[]
    });
    返回true;
    }
    返回false;
    }
    })(树木);
    const data={id:1,name:'Dog',parent_id:null,children:[{id:2,name:'Food',parent_id:1,children:[]},{id:3,name:'hood',parent_id:3,children:[]},{id:5,name:'hood',parent_id:3,children children:[]},{id:6,name:'Hydrogen',parent:'Hydrogen:'id:3,children id:3,children[]}}};
    console.log(插入(数据“Dog”、“Cat”);//正确的iff插入成功
    //=>正确
    控制台日志(数据);
    //=>{id:1,名字:'Dog',父母id:null,孩子:[{id:2,名字:'Food',父母id:1,孩子:[]},{id:3,名字:'Water',父母id:1,孩子:[{id:4,名字:'Bowl',父母id:3,孩子:[]},{id:5,名字:'Oxygen oxy',父母id:3,孩子:[]},{id:6,名字:'Hydrogen',父母id:3,孩子:[]},{id:7,名称:'Cat',父\u id:1,子:[]}
    。作为控制台包装{最大高度:100%!重要;顶部:0}

    在生成时,您可以通过闭包将侦听器直接附加到保持所需节点的按钮,然后就可以像
    node.children.push({/*…*/})
    @jonaswillms我无法影响或更改侦听器一样简单。
    let child = {
        name: "Cat",
        children: []
    };
    let maxUsedId=-1;
    _.eachDeep([data],(val)=>{
      if(val.id>maxUsedId){
        maxUsedId = val.id;
      }
    
      if(val.name==parentName){
        val.children.push(child);
        child.parent_id = val.id;
      }
    },{tree:true});
    child.id=maxUsedId+1;