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

Removing items from a nested array with unknown depth

我正在尝试根据正确匹配的数组从嵌套数组中删除项目。

满足三个要求:

  1. 数组的深度未知。项目可以嵌套 children.
  2. 只应删除没有 children 的项目
  3. 如果项目不在匹配数组中,则应将其删除

我已经构建了一个函数来递归地到达最深层次并根据 $match 数组过滤项目。

到目前为止我的代码是这样的:

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

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 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' }
    //         ]
    //       }]
    //   }];

因为 forEach 基本上“跳过”层而不返回任何内容,所以您最终得到的只是第一个也是最深的结果。

我还认为你的函数有点复杂,因为它以数组开头,而不是某种 ROOT 节点。

这是(我认为)满足您要求的替代方案:

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

在可运行的片段中:

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

我认为最好从检查名称的代码中分离出一种能够适当处理 children 的通用节点过滤技术。这里 filterNodes 接受一个谓词,说明是否应该包含该节点(不用担心 children)。然后它执行 child 处理位。

我们通过传递一个测试名称是否在允许列表中的谓词来编写我们的主函数。

加在一起,看起来像这样:

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}