简体   繁体   English

从深度未知的嵌套数组中删除项目

[英]Removing items from a nested array with unknown depth

I am trying to remove items from a nested array based on an array of correct matches.我正在尝试根据正确匹配的数组从嵌套数组中删除项目。

Three requirements apply:适用三个要求:

  1. The depth of the array is unknown.阵列的深度未知。 Items can have nested children.项可以有嵌套的子项。
  2. Only items without children should be removed只应移除没有孩子的物品
  3. The items should be removed if they are not in the matching array如果项目不在匹配的数组中,则应删除它们

I have build a function to recursively get to the deepest level and filter the items based on the $match array.我已经构建了一个函数来递归地到达最深层次并根据 $match 数组过滤项目。

This is what my code looks like so far:这是我的代码到目前为止的样子:

import * as lodash from "https://cdn.skypack.dev/lodash@4.17.21";

let filterRecursively = (arr, match) => {
  // Recursively go to the deepest array we can find
  arr.forEach(el => {
      arr = el.children ? filterRecursively(el.children, match) : arr
  });

  // If we are at the deepest level we filter the items ...
  if (arr[0] && arr[0].children === undefined) {
    return _.filter(arr, (item) => {
        return match.includes(item.name)
    })
  } else { // ... if not we just return the array as-is
    return arr
  }
}

let arr = [
  {
    'name': 'John',
    'children': [
      {
        'name': 'John',
        'children': [
          { 'name': 'John' },
          { 'name': 'Jane' },
          { 'name': 'Joe' }
        ]
      }]
  }, {
    'name': 'Jeff',
    'children': [
      {
        'name': 'Joe',
        'children': [
          { 'name': 'Jill' },
          { 'name': 'Jeff' },
          { 'name': 'Joe' }
        ]
      }]
  }];

let match = ['John', 'Joe'];
let result = filterRecursively(arr, match);

console.log(result);

// Expected result:
 [
   {
     'name': 'John',
     'children': [
       {
         'name': 'John',
         'children': [
           { 'name': 'John' },
           { 'name': 'Joe' }
         ]
       }]
   }, {
     'name': 'Jeff',
     'children': [
       {
         'name': 'Joe',
         'children': [
           { 'name': 'Joe' }
         ]
       }]
   }];
// Current output
[
    {
        "name": "Joe"
    }
]

See the Codepen 见 Codepen

let filterRecursively = (arr, match) => {
  // Recursively go to the deepest array we can find
  return arr
    .map((el) =>
      el.children
        ? { ...el, children: filterRecursively(el.children, match) }
        : el
    )
    .filter((el) => el.children || match.includes(el.name));
};

I have updated filterRecursively.我已经更新了过滤器递归。

 let filterRecursively = (arr, match) => { // Recursively go to the deepest array we can find return arr .map((el) => el.children ? { ...el, children: filterRecursively(el.children, match) } : el ) .filter((el) => el.children || match.includes(el.name)); }; let arr = [ { name: "John", children: [ { name: "John", children: [{ name: "John" }, { name: "Jane" }, { name: "Joe" }], }, ], }, { name: "Jeff", children: [ { name: "Joe", children: [{ name: "Jill" }, { name: "Jeff" }, { name: "Joe" }], }, ], }, ]; let match = ["John", "Joe"]; let result = filterRecursively(arr, match); console.log(JSON.stringify(result)); // Expected result: // [ // { // 'name': 'John', // 'children': [ // { // 'name': 'John', // 'children': [ // { 'name': 'John' }, // { 'name': 'Joe' } // ] // }] // }, { // 'name': 'Jeff', // 'children': [ // { // 'name': 'Joe', // 'children': [ // { 'name': 'Joe' } // ] // }] // }];

I think it's better to separate out a generic node filtering technique that handles children appropriately from the code that checks the names.我认为最好从检查名称的代码中分离出适当处理子节点的通用节点过滤技术。 Here filterNodes accepts a predicate that says whether the node should be included (without worrying about children).这里filterNodes接受一个谓词,表示是否应该包含节点(不用担心子节点)。 It then does the child handling bit.然后它会处理孩子。

We write our main function by just passing a predicate that tests whether the name is on the allowed list.我们只通过传递一个谓词来测试名称是否在允许列表中来编写我们的 main 函数。

Together, it looks like this:在一起,它看起来像这样:

 const filterNodes = (pred) => (nodes) => nodes .flatMap ( (node, _, __, kids = filterNodes (pred) (node .children || []) ) => pred (node) || node .children ?.length > 0 ? [{...node, ... (kids .length ? {children: kids} : {})}] : [] ) const removeUnmatchedNames = (names) => filterNodes (({name}) => names .includes (name)) const arr = [{name: "John", children: [{name: "John", children: [{name: "John"}, {name: "Jane"}, {name: "Joe"}]}]}, {name: "Jeff", children: [{name: "Joe", children: [{name: "Jill"}, {name: "Jeff"}, {name: "Joe"}]}]}] console .log (removeUnmatchedNames (['John', 'Joe']) (arr))
 .as-console-wrapper {max-height: 100% !important; top: 0}

Because the forEach basically "skips" layers without returning anything, you end up with just your first and deepest result.因为forEach基本上“跳过”层而不返回任何内容,所以您最终只会得到第一个也是最深的结果。

I also think your function is a bit more complicated because it starts with an array, rather than a sort of ROOT node.我还认为您的函数有点复杂,因为它以数组开头,而不是某种ROOT节点。

Here's an alternative that (I think) meets your requirements:这是(我认为)满足您要求的替代方案:

let childlessMatch = (node, match) => {
  // If it's at the deepest level, check against match
  if (node.children === undefined) {
    return match.includes(node.name) ? [node] : [];
  }
  
  // If not, calculate the next child layer first
  const newChildren = node.children.flatMap(c => childlessMatch(c, match));

  // With the children calculated, we can prune based on whether there
  // are any children left to show
  if (newChildren.length === 0) return [];
  
  return [{
    ...node,
    children: newChildren
  }]
} 

In a runnable snippet:在一个可运行的片段中:

 let childlessMatch = (node, match) => { if (node.children === undefined) { return match.includes(node.name) ? [node] : []; } const newChildren = node.children.flatMap(c => childlessMatch(c, match)); if (newChildren.length === 0) return []; return { ...node, children: newChildren } } let arr = [ { 'name': 'John', 'children': [ { 'name': 'John', 'children': [ { 'name': 'John' }, { 'name': 'Jane' }, { 'name': 'Joe' } ] }] }, { 'name': 'Jeff', 'children': [ { 'name': 'Joe', 'children': [ { 'name': 'Jill' }, { 'name': 'Jeff' }, { 'name': 'Joe' } ] }] }]; let match = ['John', 'Joe']; let result = childlessMatch({ children: arr }, match).children; console.log(result);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM