递归地找到孩子的孩子并删除

时间:2019-05-14 14:56:28

标签: javascript arrays recursion

我有一个有孩子的孩子的数组,并且一切都由parentId关联。

示例:

[
{id: 1, parentid:0},{id: 2, parentid:1},
{id: 3, parentid:2},{id: 4, parentid:2},{id: 10, parentid:4},
{id: 5, parentid:0},{id: 6, parentid:5},{id: 7, parentid:7}
]

我想使用Id:1及其所有相关子对象删除该对象。 这样就是这些物体

{id: 1, parentid:0},{id: 2, parentid:1},
{id: 3, parentid:2},{id: 4, parentid:2},{id: 10, parentid:4}

6 个答案:

答案 0 :(得分:5)

这是使用递归实现的一种实用方法。编号的项目符号点与下面的代码中的编号注释匹配。

  1. (基本)没有node,因此无可处理。返回结果r
  2. (归纳)至少有一个节点。如果节点的idparentid在集合s中,则找到匹配的节点。将节点的id添加到集合中,并从部分结果r和其余节点more开始搜索。
  3. (归纳法)至少有一个节点,它与我们要搜索的ID不匹配。将节点添加到结果中,然后继续搜索more个节点。

const removeFamily =
  ( id = 0
  , [ node, ...more ] = []
  , s = new Set ([ id ])
  , r = []
  ) =>
    node === undefined
      ? r                               // 1
      : s .has (node.id) || s .has (node.parentid)
          ? removeFamily                // 2
              ( id
              , [ ...r, ...more ]
              , s .add (node.id)
              , []
              )
          : removeFamily                // 3
              ( id
              , more
              , s
              , [ ...r, node ]
              )

const nodes =
  [ { id: 1, parentid: 0 }
  , { id: 2, parentid: 1 }
  , { id: 3, parentid: 2 }
  , { id: 4, parentid: 2 }
  , { id: 10, parentid: 4 }
  , { id: 5, parentid: 0 }
  , { id: 6, parentid: 5 }
  , { id: 7, parentid: 7 }
  ]

const newNodes =
  removeFamily (1, nodes)

console .log (newNodes)
// [ { id: 5, parentid: 0 }
// , { id: 6, parentid: 5 }
// , { id: 7, parentid: 7 }
// ]

如果可以帮助您更好地查看它,请使用if语句重写它-

const removeFamily =
  ( id = 0
  , [ node, ...more ] = []
  , s = new Set ([ id ])
  , r = []
  ) =>
  { if (node === undefined)
      return r               // 1
    else if (s .has (node.id) || s .has (node.parentid))
      return removeFamily    // 2
        ( id
        , [ ...r, ...more ]
        , s .add (node.id)
        , []
        )
    else
      return removeFamily    // 3
       ( id
       , more
       , s
       , [ ...r, node ]
       )
  }

这是一个使用通用loop / recur接口的堆栈安全变体。即使节点列表可以包含数百万个节点,此版本也可以使用。它还具有稍微更好的公共接口,因为只有两(2)个参数可以在呼叫站点进行配置-

const recur = (...values) =>
  ({ recur, values })

const loop = f =>
{ let a = f ()
  while (a && a.recur === recur)
    a = f (...a.values)
  return a
}

const removeFamily = (id = 0, nodes = []) =>
  loop
    ( ( [ node, ...more ] = nodes
      , s = new Set ([ id ])
      , r = [] 
      ) =>
        node === undefined
          ? r                           // 1
          : s .has (node.id) || s .has (node.parentid)
            ? recur                     // 2
                ( [ ...r, ...more ]
                , s .add (node.id)
                , []
                )
            : recur                     // 3
                ( more
                , s
                , [ ...r, node ]
                )
    )

const nodes =
  [ { id: 1, parentid: 0 }
  , { id: 2, parentid: 1 }
  , { id: 3, parentid: 2 }
  , { id: 4, parentid: 2 }
  , { id: 10, parentid: 4 }
  , { id: 5, parentid: 0 }
  , { id: 6, parentid: 5 }
  , { id: 7, parentid: 7 }
  ]


const newNodes =
  removeFamily (1, nodes)

console .log (newNodes)
// [ { id: 5, parentid: 0 }
// , { id: 6, parentid: 5 }
// , { id: 7, parentid: 7 }
// ]

答案 1 :(得分:2)

对于关系,您可以选择Map,对于删除的所有id,可以选择Generator

function* remove(id) {
    yield id;
    for (id of relations.get(id) || []) yield* remove(id);    
}

var data = [{ id: 1, parentid: 0 }, { id: 2, parentid: 1 }, { id: 3, parentid: 2 }, { id: 4, parentid: 2 }, { id: 10, parentid: 4 }, { id: 5, parentid: 0 }, { id: 6, parentid: 5 }, { id: 7, parentid: 7 }],
    relations = data.reduce((m, { id, parentid }) => m.set(parentid, [...(m.get(parentid) || []), id]), new Map),
    id = 1,
    ids = [...remove(id)],
    i = data.length;
    
while (i--)
    if (ids.includes(data[i].id))
        data.splice(i, 1);

console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }

答案 2 :(得分:1)

摘要

您基本上是在修剪家谱。由于没有显式的树数据结构,这项工作变得很复杂。相反,树结构是由一组本地父母关系所暗示的(为您提供这些关系的数组的条目可以按任何顺序排序)。

您可以首先构建一个真正的树结构,然后使用.parentid === 1删除所有节点(保留您的示例),并消除所有后代。 可以通过不构建其根为.parentid === 1的子树来优化此过程。

以下建议比较简单。该代码反复搜索已知已消除的节点的子节点,直到不再找到新的此类子节点为止。因此,它可以跟踪字典中当前已知的后代。

这个简单的想法是以最坏情况下的O(n^2)运行时间为代价的,n是原始数组中条目的数量。

该算法是尾递归的实例,因此可以将递归示意性地转换为循环。

请注意,[p]bdict_seen字典实际上可以删除,因为它的更新确实反映了[p]bdict_descendants字典的更新。

运行代码(对于给定的示例):

    在浏览器中
  • :将其放入控制台并单击CR。
  • 在node.js下:运行'node <thisfile>.js'

代码

    let ao_nodes =  [                                                                      
     {id: 1, parentid:0},{id: 2, parentid:1},                               
     {id: 3, parentid:2},{id: 4, parentid:2},{id: 10, parentid:4},          
     {id: 5, parentid:0},{id: 6, parentid:5},{id: 7, parentid:7}            
    ];                                                                      



    function demo_kernel ( pbdict_descendants, pbdict_seen ) {
        let b_foundsome = false
          ;

        //
        //  For all nodes:
        //     If not yet identified as a descendant and its parent is among the set of known ancestors, add it to the set of descendants.
        //      
        for (let o_node of ao_nodes ) {
            if (!pbdict_seen.hasOwnProperty ( o_node.id )) { // using 'pbdict_descendants' for this test is equivalent; in doing so, [p]bdict_seen can be removed from the code altogether.  
                if (pbdict_descendants.hasOwnProperty ( o_node.parentid )) {
                    b_foundsome = true;
                    pbdict_descendants[o_node.id] = true;
                    pbdict_seen[o_node.id]      = true;
                }
            }
        }
        
        //
        //  At least 1 new descendant has been found on this level.
        //  If no more descendants are found, this marks the end of the recursion.
        //
        if (b_foundsome) {
            demo_kernel ( pbdict_descendants, pbdict_seen );
        }
    } // demo_kernel

    function demo_kernel_nonrec ( pbdict_descendants, pbdict_seen ) {
        let b_foundsome = true
          ;

        //
        //  For all nodes:
        //     If not yet identified as a descendant and its parent is among the set of known ancestors, add it to the set of descendants.
        //
        while (b_foundsome) {
            b_foundsome = false;
            for (let o_node of ao_nodes ) {
                if (!pbdict_seen.hasOwnProperty ( o_node.id )) { // using 'pbdict_descendants' for this test is equivalent; in doing so, [p]bdict_seen can be removed from the code altogether.  
                    if (pbdict_descendants.hasOwnProperty ( o_node.parentid )) {
                        b_foundsome = true;
                        pbdict_descendants[o_node.id] = true;
                        pbdict_seen[o_node.id]      = true;
                    }
                }
            }
        }      
    } // demo_kernel_nonrec


    function demo ( ps_id ) {
        let ao_purged
          , bdict_descendants
          , bdict_seen
          ;
        
        //
        // Register start node
        //
        bdict_descendants = {
            [ps_id]: true
        };
        bdict_seen = {
            [ps_id]: true
        };
        
        //
        // identify descendants.
        //  Express recursion recursion 
        //
        //  Use either one of the next two lines
        //      demo_kernel:        recursive (demonstration purpose only)
        //      demo_kernel_nonrec: non-recursive (use this one)
        //
        //*** demo_kernel ( bdict_descendants, bdict_seen ); 
        demo_kernel_nonrec ( bdict_descendants, bdict_seen );
        
        //
        //  Compile result: produce the purged set of nodes. 
        //
        ao_purged = [];
        for (let o_node of ao_nodes ) {
            if (!bdict_descendants.hasOwnProperty ( o_node.id )) {
                ao_purged.push ( o_node );
            }
        }
        
        return ao_purged;    
    }

    let n_root = 1
      ;

    console.log ( `original:\n${JSON.stringify(ao_nodes)}.\n\n` ); 
    console.log ( `purged (root: ${n_root}):\n${JSON.stringify(demo ( n_root ))}.\n` ); // Prints to the browser console.

答案 3 :(得分:0)

使用ES6进行递归深度

没有测试,但是会是这样

var dataStore = [{}] // filled in with your data

function removeDataWithRelationships(id) {
     // find root parent to remove
     var itemToRemoveIndex = dataStore.findIndex(ds => ds.id === id);

     // grab reference to remove
     var currentReference = dataStore[itemToRemoveIndex]

     // remove current item
     dataStore.splice(itemToRemoveIndex,1);

     // look for children on currentReference
     var childrenToRemove = dataStore.find(ds => ds.parentid === currentReference.id);

     // if there are children, look for parents and run recursive operation
     if (childrenToRemove) {     
           //recursively call this function to remove all children
          childrenToRemove.forEach(id =>  {
               removeDataWithRelationship(id);
          });
     }
}

答案 4 :(得分:0)

您想要的是深度优先(或广度优先)搜索。因此,您可以使用DFS查找所有子节点和子节点,然后在找到所有子节点和子节点后将其过滤掉。

function removeFromID(id) {
    let stack = [], found = [];
    children.forEach(child => {
        if (child.id === id) {
          found.push(child);
          stack.push(child);
    });
    while (stack.length > 0) {
      let current = stack.pop();
      children.forEach(child => {
        if (child.id === current.id) {
          stack.push(child);
          found.push(child);
        }
      });
    }
    children = children.filter(child => found.indexOf(child) <= -1);
}

答案 5 :(得分:0)

此解决方案不依赖于起始数组的排序顺序。也就是说,可以在数组的开头找到孙子代。

代码中有解释(将数组改写为在前面有granchildren):

// this array is rewritten so granchildren appear first!
let arr = [ 
    {id: 3, parentid:2},{id: 4, parentid:2},{id: 10, parentid:4},
    {id: 5, parentid:0},{id: 6, parentid:5},{id: 7, parentid:7},
    {id: 1, parentid:0},{id: 2, parentid:1}
]

function remove(id, arr) {
    //first time it's called id is put into an array
    let del = (Array.isArray(id))? id: [id]

    let newArr = []

    arr.forEach((obj) => {
        switch (true) {
            // removes topmost parent
            case del.includes(obj.id):
                break;
            // removes subsequent children
            case del.includes(obj.parentid):
                del.push(obj.id)
                break;
            // retains the rest
            default:
                newArr.push(obj)
        }
    })

    // if this pass did remove something, we call function again 
    // since original array may not be sorted and deep grandchildren
    // are found in the beginning of the array
    if (arr.length !== newArr.length) {newArr = remove(del, newArr)}

    // when no further changes are made, we return the result
    return newArr
}

console.log(remove(1, arr)) //results in [{id:5,parentid:0}, {id:6,parentid:5},{id:7,parentid:7}