从深层嵌套的对象数组中删除匹配的对象

问题描述 投票:0回答:4

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

{  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集中删除所选项作为变量clickedTitle。我无法找到一个允许我从深度嵌套的树中删除正确的对象数据的算法。

这是我的代码:

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;
}

我试图使用来自Removing matched object from array of objects using javascript的信息但没有成功。

javascript arrays object
4个回答
2
投票

如果你不介意修改参数树in-place,这应该可以胜任。请注意,如果您尝试删除根,它将返回null

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"));

您当前的代码非常正确。然而:

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

突出了一些问题:我们需要操纵父的children数组去除curNode而不是curNode自己的children数组。我通过调用递归传递父对象和子索引,省去了线性操作findIndex的麻烦。

此外,从indexindex + 1的切片仅提取一个元素而不会修改curNode.children。如何使用newArray或通过调用堆栈返回它并不明显。 splice似乎是一个更适合手头任务的工具:就地提取一个元素。

请注意,此函数将删除与nameToDelete匹配的多个条目。


1
投票

我已经构建了如下算法:

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
  }  
}

您可以使用它返回没有项目的新树:

noHydrogen = omitNodeWithName(tree, "Hydrogen")

0
投票

我喜欢@ VictorNascimento的答案,但是通过应用map然后filter,每个children列表将被迭代两次。以下是reduce的替代方案,以避免这种情况:

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

如果您希望有一种方法来移除就地项目,正如@ggorlen建议的那样,我建议使用以下解决方案,我认为这更简单:

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
}

0
投票

如果可以使用Lodash + Deepdash,那么:

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

这是一个Codepen

© www.soinside.com 2019 - 2024. All rights reserved.