2

I have a data tree structure with children:

{  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: []
                 }
             ]
         }
   ]
}

This represents a DOM structure that a user could select an item from to delete by clicking the corresponding button in the DOM.

I have a known text title of the selected item for deletion from the DOM set as the variable clickedTitle. I am having trouble finding an algorithm that will allow me to delete the correct object data from the deeply nested tree.

Here is my code:

function askUserForDeleteConfirmation(e) {
    const okToDelete = confirm( 'Are you sure you want to delete the item and all of its sub items?' );
    if(!okToDelete) {
        return;
    }
    const tree = getTree(); // returns the above data structure
    const clickedTitle = getClickedTitle(e); // returns string title of clicked on item from DOM - for example "Dog" or "Bowl"
    const updatedTree = removeFromTree(tree, tree, clickedTitle);

    return updatedTree;
}

function removeFromTree(curNode, newTree, clickedTitle) {
    if(curNode.name === clickedTitle) {
        // this correctly finds the matched data item to delete but the next lines don't properly delete it... what to do?
        const index = curNode.children.findIndex(child => child.name === clickedTitle);
        newTree = curNode.children.slice(index, index + 1);
        // TODO - what to do here?
    }

    for(const node of curNode.children) {
        removeFromTree(node, newTree, clickedTitle);
    }

    return newTree;
}

I have tried to use the info from Removing matched object from array of objects using javascript without success.

5
  • Could you provide what would be the desired result in the application of the function for an example case? Commented Mar 9, 2019 at 23:29
  • 1
    having both checks of curNode.name === clickedTitle and child => child.name === clickedTitle doesn't make sense to me. Commented Mar 9, 2019 at 23:31
  • Exacty, that´s why I am asking for an example of the desired effect Commented Mar 9, 2019 at 23:35
  • the desired result is to remove the item from the original tree. Commented Mar 10, 2019 at 14:26
  • ggorlen's answer worked without any issues. Victor's answer did seem to work but the application wasn't working out, was causing weird behaviors. Commented Mar 10, 2019 at 14:27

5 Answers 5

2

If you don't mind modifying the parameter tree in-place, this should do the job. Note that it'll return null if you attempt to remove the root.

const tree = { 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: [] } ] } ] };

const removeFromTree = (root, nameToDelete, parent, idx) => {
  if (root.name === nameToDelete) {
    if (parent) {
      parent.children.splice(idx, 1);
    }
    else return null;
  }
  
  for (const [i, e] of root.children.entries()) {
    removeFromTree(e, nameToDelete, root, i);
  }
  
  return tree;
};

console.log(removeFromTree(tree, "Oxygen"));

Your current code is very much on the right track. However:

newTree = curNode.children.slice(index, index + 1);

highlights a few issues: we need to manipulate the parent's children array to remove curNode instead of curNode's own children array. I pass parent objects and the child index recursively through the calls, saving the trouble of the linear operation findIndex.

Additionally, slicing from index to index + 1 only extracts one element and doesn't modify curNode.children. It's not obvious how to go about using newArray or returning it through the call stack. splice seems like a more appropriate tool for the task at hand: extracting one element in-place.

Note that this function will delete multiple entries matching nameToDelete.

Sign up to request clarification or add additional context in comments.

Comments

2

I like @VictorNascimento's answer, but by applying map then filter, each children list would be iterated twice. Here is an alternative with reduce to avoid that:

function removeFromTree(node, name) {
  return node.name == name
    ? undefined
    : {
        ...node,
        children: node.children.reduce(
          (children, child) => children.concat(removeFromTree (child, name) || []), [])
      }
}

In the case you want a way to remove the items in-place, as @ggorlen proposed, I'd recommend the following solution, that is simpler in my opinion:

function removeFromTree(node, name) {
  if (node.name == name) {
    node = undefined
  } else {
    node.children.forEach((child, id) => {
      if (!removeFromTree(child, name)) node.children.splice(id, 1)
    })
  }
  return node
}

Comments

1

I've built the algorithm as follows:

function omitNodeWithName(tree, name) {
  if (tree.name === name) return undefined;

  const children = tree.children.map(child => omitNodeWithName(child, name))
    .filter(node => !!node);

  return {
    ...tree,
    children
  }  
}

You can use it to return a new tree without the item:

noHydrogen = omitNodeWithName(tree, "Hydrogen")

Comments

0

If it's ok to use Lodash+Deepdash, then:

let cleaned = _.filterDeep([tree],(item)=>item.name!='Hydrogen',{tree:true});

Here is a Codepen

Comments

0

We use object-scan for many data processing tasks. It's powerful once you wrap your head around it. Here is how you could answer your question

// const objectScan = require('object-scan');

const prune = (name, input) => objectScan(['**[*]'], {
  rtn: 'bool',
  abort: true,
  filterFn: ({ value, parent, property }) => {
    if (value.name === name) {
      parent.splice(property, 1);
      return true;
    }
    return false;
  }
})(input);

const obj = { 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: [] }] }] };

console.log(prune('Oxygen', obj)); // return true iff pruned
// => true

console.log(obj);
// => { 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: 6, name: 'Hydrogen', parent_id: 3, children: [] } ] } ] }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/[email protected]"></script>

Disclaimer: I'm the author of object-scan

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.