0

I have a tree object like below:

let data = [
  {
    id: 1,
    children: [
     {
      id: 1.1,
      children: [
       {
        id: 1.2,
        children: []
       },
       {
        id: 1.22,
        children: []
        }
      ]
     }
    ]
  },
  {
   id: 2,
   children: []
  }
]

I want to filter out id equal a specific value. In this case, I want to filter out id equal 1.2.

The rusult I want is like below:

let data = [
  {
    id: 1,
    children: [
     {
      id: 1.1,
      children: [
        {
         id: 1.22,
         children: []
        }
      ]
     }
    ]
  },
  {
   id: 2,
   children: []
  }
]

I have search a few question about filter nest deep object, But still don't know how. I need to use recursion to solve this.

This is my way:

function handleDelete (data) {
 return data.filter(t => {
   if (t.children.length) {
     handleDelete(t.children)
    })
   } else {
     return t.id !== '1.2'
  }
})
}
let result = handleDelete(data)
Anderson Min
  • 89
  • 2
  • 12

1 Answers1

1

delete a node and its descendants

Here's an effective technique using flatMap and mutual recursion1 -

  1. del accepts an array of nodes, t, a query, q, and calls del1 on each node with the query
  2. del1 accepts a single node, t, a query, q, and calls del on a node's children

const del = (t, q) =>
  t.flatMap(v => del1(v, q))                  // <-- 1

const del1 = (t, q) =>
  q == t.id
    ? []
    : { ...t, children: del(t.children, q) }  // <-- 2

const data =
  [{id:1,children:[{id:1.1,children:[{id:1.2,children:[]},{id:1.22,children:[]}]}]},{id:2,children:[]}]

const result =
  del(data, "1.2")

console.log(result)

In the output, we see node.id 1.2 is removed -

[
  {
    "id": 1,
    "children": [
      {
        "id": 1.1,
        "children": [
          {
            "id": 1.22,
            "children": []
          }
        ]
      }
    ]
  },
  {
    "id": 2,
    "children": []
  }
]

preserving the descendants

In the program above, if a node.id matches our query, the node and all of its descendent children are removed. If we only want to delete the parent node and keep the children, we can make a single modification (!) to the program -

const del = (t, q) =>
  t.flatMap(v => del1(v, q))

const del1 = (t, q) =>
  q == t.id
    ? del(t.children, q)                      // <-- !
    : { ...t, children: del(t.children, q) }

const data =
  [{id:1,children:[{id:1.1,children:[{id:1.2,children:[]},{id:1.22,children:[]}]}]},{id:2,children:[]}]

const result =
  del(data, "1")     // <-- delete node.id equal to "1"

console.log(result)

Notice how the children for 1 are still included in the output -

[
  {
    "id": 1.1,
    "children": [
      {
        "id": 1.2,
        "children": []
      },
      {
        "id": 1.22,
        "children": []
      }
    ]
  },
  {
    "id": 2,
    "children": []
  }
]

without mutual recursion

Mutual recursion isn't the only way to do it, but it's the only way to avoid a dynamic type check, such as the one below. In this final revision, we remove a parent and all of its children, as we did in the first program, but this del is implemented using a single recursive function -

const del = (t, q) =>
  Array.isArray(t)                             // <-- array
    ? t.flatMap(v => del(v, q))
: Object(t) === t                              // <-- object
    ? q == t.id
      ? []
      : { ...t, children: del(t.children, q) }
: t                                            // <-- neither (noop)


const data =
  [{id:1,children:[{id:1.1,children:[{id:1.2,children:[]},{id:1.22,children:[]}]}]},{id:2,children:[]}]

const result =
  del(data, "1.2")

console.log(result)

The output is the same as the first program, where 1.2 and all descendants are removed -

[
  {
    "id": 1,
    "children": [
      {
        "id": 1.1,
        "children": [
          {
            "id": 1.22,
            "children": []
          }
        ]
      }
    ]
  },
  {
    "id": 2,
    "children": []
  }
]

1. See this technique used on a different data set in this related Q&A.
2. All programs in this answer produce a new tree. The original input is not modified by del (or del1).

Mulan
  • 129,518
  • 31
  • 228
  • 259
  • It took me hours to understand what you meaning. Can you explain more about `Object(t) === t `, how to check is object? The constructor Object(), what does it mean? thanks – Anderson Min Sep 08 '20 at 07:30
  • [https://stackoverflow.com/questions/24460874/what-does-objectobj-obj-do#:~:text=2%20Answers&text=Object(obj)%20%3D%3D%3D%20obj%20tests,failing%20also%20for%20strings%2C%20etc.&text=1)%20If%20value%20is%20null,2.1).] I found this link about Object(obj) === obj – Anderson Min Sep 08 '20 at 08:13
  • Correct, it's is a way of checking if `t` is an object. However, runtime type checking like `Array.isArray(t)` and `Object(t) === t` is a bit clunky and can sometimes indicate a function is trying to do too many things. The first program avoids type checks by using a combination of smaller functions that each do one thing well. – Mulan Sep 08 '20 at 20:09