繁体   English   中英

递归过滤对象数组

[英]Recursively filter array of objects

用这个撞墙,我想我会把它贴在这里,以防有好心人遇到类似的。 我有一些看起来像这样的数据:

const input = [
  {
    value: 'Miss1',
    children: [
      { value: 'Miss2' },
      { value: 'Hit1', children: [ { value: 'Miss3' } ] }
    ]
  },
  {
    value: 'Miss4',
    children: [
      { value: 'Miss5' },
      { value: 'Miss6', children: [ { value: 'Hit2' } ] }
    ]
  },
  {
    value: 'Miss7',
    children: [
      { value: 'Miss8' },
      { value: 'Miss9', children: [ { value: 'Miss10' } ] }
    ]
  },
  {
    value: 'Hit3',
    children: [
      { value: 'Miss11' },
      { value: 'Miss12', children: [ { value: 'Miss13' } ] }
    ]
  },
  {
    value: 'Miss14',
    children: [
      { value: 'Hit4' },
      { value: 'Miss15', children: [ { value: 'Miss16' } ] }
    ]
  },
];

我不知道在运行时层次结构有多深,即有多少级别的对象将有一个子数组。 我已经稍微简化了这个例子,我实际上需要将 value 属性与一组搜索词进行匹配。 让我们暂时假设我匹配 where value.includes('Hit')

我需要一个返回新数组的函数,例如:

  • 每个没有子级或子级层次结构中没有匹配项的非匹配对象不应存在于输出对象中

  • 每个具有包含匹配对象的后代的对象都应该保留

  • 匹配对象的所有后代都应该保留

在这种情况下,我正在考虑将“匹配对象”作为包含字符串Hitvalue属性的对象,反之亦然。

输出应如下所示:

const expected = [
  {
    value: 'Miss1',
    children: [
      { value: 'Hit1', children: [ { value: 'Miss3' } ] }
    ]
  },
  {
    value: 'Miss4',
    children: [
      { value: 'Miss6', children: [ { value: 'Hit2' } ] }
    ]
  },
  {
    value: 'Hit3',
    children: [
      { value: 'Miss11' },
      { value: 'Miss12', children: [ { value: 'Miss13' } ] }
    ]
  },
  {
    value: 'Miss14',
    children: [
      { value: 'Hit4' },
    ]
  }
];

非常感谢花时间读到这里的任何人,如果我先到达那里,将发布我的解决方案。

使用.filter()并进行递归调用,就像我在上面的评论中所描述的那样基本上是您所需要的。 您只需要在返回之前使用递归调用的结果更新每个.children属性。

返回值只是结果.children集合的.length ,因此如果至少有一个,则保留该对象。

var res = input.filter(function f(o) {
  if (o.value.includes("Hit")) return true

  if (o.children) {
    return (o.children = o.children.filter(f)).length
  }
})

 const input = [ { value: 'Miss1', children: [ { value: 'Miss2' }, { value: 'Hit1', children: [ { value: 'Miss3' } ] } ] }, { value: 'Miss4', children: [ { value: 'Miss5' }, { value: 'Miss6', children: [ { value: 'Hit2' } ] } ] }, { value: 'Miss7', children: [ { value: 'Miss8' }, { value: 'Miss9', children: [ { value: 'Miss10' } ] } ] }, { value: 'Hit3', children: [ { value: 'Miss11' }, { value: 'Miss12', children: [ { value: 'Miss13' } ] } ] }, { value: 'Miss14', children: [ { value: 'Hit4' }, { value: 'Miss15', children: [ { value: 'Miss16' } ] } ] }, ]; var res = input.filter(function f(o) { if (o.value.includes("Hit")) return true if (o.children) { return (o.children = o.children.filter(f)).length } }) console.log(JSON.stringify(res, null, 2))


请注意,字符串上的.includes()是 ES7,因此可能需要针对旧版浏览器进行修补。 您可以使用传统的.indexOf("Hit") != -1代替它。


为了不改变原始对象,创建一个映射函数来复制一个对象并在过滤器之前使用它。

function copy(o) {
  return Object.assign({}, o)
}

var res = input.map(copy).filter(function f(o) {
  if (o.value.includes("Hit")) return true

  if (o.children) {
    return (o.children = o.children.map(copy).filter(f)).length
  }
})

要真正压缩代码,您可以这样做:

var res = input.filter(function f(o) {
  return o.value.includes("Hit") ||
         o.children && (o.children = o.children.filter(f)).length
})

虽然读起来有点难。

这是一个可以完成您正在寻找的功能的功能。 本质上,它将测试arr中的每个项目是否匹配,然后递归调用过滤器的children 还使用Object.assign以便不更改基础对象。

function filter(arr, term) {
    var matches = [];
    if (!Array.isArray(arr)) return matches;

    arr.forEach(function(i) {
        if (i.value.includes(term)) {
            matches.push(i);
        } else {
            let childResults = filter(i.children, term);
            if (childResults.length)
                matches.push(Object.assign({}, i, { children: childResults }));
        }
    })

    return matches;
}

我认为这将是一个递归解决方案。 这是我尝试过的一种。

function find(obj, key) {
  if (obj.value && obj.value.indexOf(key) > -1){
    return true;
  }
  if (obj.children && obj.children.length > 0){
    return obj.children.reduce(function(obj1, obj2){
      return find(obj1, key) || find(obj2, key);
    }, {}); 
  } 
  return false;
}

var output = input.filter(function(obj){
     return find(obj, 'Hit');
 });
console.log('Result', output);

 const input = [ { value: 'Miss1', children: [ { value: 'Miss1' }, { value: 'Hit1', children: [ { value: 'Miss3' } ] } ] }, { value: 'Miss4', children: [ { value: 'Miss5' }, { value: 'Miss6', children: [ { value: 'Hit2' } ] } ] }, { value: 'Miss7', children: [ { value: 'Miss8' }, { value: 'Miss9', children: [ { value: 'Miss10' } ] } ] }, { value: 'Hit3', children: [ { value: 'Miss11' }, { value: 'Miss12', children: [ { value: 'Miss13' } ] } ] }, { value: 'Miss14asds', children: [ { value: 'Hit4sdas' }, { value: 'Miss15', children: [ { value: 'Miss16' } ] } ] }, ]; function filter(arr, term) { var matches = []; if (!Array.isArray(arr)) return matches; arr.forEach(function(i) { if (i.value === term) { const filterData = (i.children && Array.isArray(i.children))? i.children.filter(values => values.value ===term):[]; console.log(filterData) i.children =filterData; matches.push(i); } else { // console.log(i.children) let childResults = filter(i.children, term); if (childResults.length) matches.push(Object.assign({}, i, { children: childResults })); } }) return matches; } const filterData= filter(input,'Miss1'); console.log(filterData)

下面使用递归函数过滤父子数组数据的代码

 const input = [ { value: 'Miss1', children: [ { value: 'Miss2' }, { value: 'Hit1', children: [ { value: 'Miss3' } ] } ] }, { value: 'Miss4', children: [ { value: 'Miss5' }, { value: 'Miss6', children: [ { value: 'Hit2' } ] } ] }, { value: 'Miss7', children: [ { value: 'Miss8' }, { value: 'Miss9', children: [ { value: 'Miss10' } ] } ] }, { value: 'Hit3', children: [ { value: 'Miss11' }, { value: 'Miss12', children: [ { value: 'Miss13' } ] } ] }, { value: 'Miss14', children: [ { value: 'Hit4' }, { value: 'Miss15', children: [ { value: 'Miss16' } ] } ] }, ]; var res = input.filter(function f(o) { if (o.value.includes("Hit")) return true if (o.children) { return (o.children = o.children.filter(f)).length } }) console.log(JSON.stringify(res, null, 2))

Array.prototype.flatMap非常适合递归过滤。 类似mapfilterreduce使用flatMap修改原始输入-

 const findHits = (t = []) => t.flatMap(({ value, children }) => { if (value.startsWith("Hit")) return [{ value, children }] else { const r = findHits(children) return r.length ? [{ value, children: r }] : [] } }) const input = [{value:'Miss1',children:[{value:'Miss2'},{value:'Hit1', children:[{value:'Miss3'}]}]},{value:'Miss4',children:[{value:'Miss5'},{value:'Miss6', children:[{value:'Hit2'}]}]},{value:'Miss7',children:[{value:'Miss8'},{value:'Miss9', children:[{value:'Miss10'}]}]},{value:'Hit3',children:[{value:'Miss11'},{value:'Miss12', children:[{value:'Miss13'}]}]},{value:'Miss14',children:[{value:'Hit4'},{value:'Miss15', children:[{value:'Miss16'}]}]}] const result = findHits(input) console.log(JSON.stringify(result, null, 2))

[
  {
    "value": "Miss1",
    "children": [
      {
        "value": "Hit1",
        "children": [
          {
            "value": "Miss3"
          }
        ]
      }
    ]
  },
  {
    "value": "Miss4",
    "children": [
      {
        "value": "Miss6",
        "children": [
          {
            "value": "Hit2"
          }
        ]
      }
    ]
  },
  {
    "value": "Hit3",
    "children": [
      {
        "value": "Miss11"
      },
      {
        "value": "Miss12",
        "children": [
          {
            "value": "Miss13"
          }
        ]
      }
    ]
  },
  {
    "value": "Miss14",
    "children": [
      {
        "value": "Hit4"
      }
    ]
  }
]

或者,您可以使用_.filterDeep deepdash扩展中的lodash

var keyword = 'Hit';
var foundHit = _.filterDeep(
  input,
  function(value) {
    return value.value.includes(keyword);
  },
  {
    tree: true,
    onTrue: { skipChildren: true },
  }
);

这是对您的案例的完整测试

这是一个很好的解决方案,它利用了数组reduce函数,这导致比其他解决方案更具可读性的代码。 在我看来,它也更具可读性。 我们递归地调用filter函数来过滤一个数组及其子元素

 const input = [ { value: "Miss1", children: [ { value: "Miss2" }, { value: "Hit1", children: [{ value: "Miss3" }] }, ], }, { value: "Miss4", children: [ { value: "Miss5" }, { value: "Miss6", children: [{ value: "Hit2" }] }, ], }, { value: "Miss7", children: [ { value: "Miss8" }, { value: "Miss9", children: [{ value: "Miss10" }] }, ], }, { value: "Hit3", children: [ { value: "Miss11" }, { value: "Miss12", children: [{ value: "Miss13" }] }, ], }, { value: "Miss14", children: [ { value: "Hit4" }, { value: "Miss15", children: [{ value: "Miss16" }] }, ], }, ]; function recursiveFilter(arr) { return arr.reduce(function filter(prev, item) { if (item.value.includes("Hit")) { if (item.children && item.children.length > 0) { return prev.concat({ ...item, children: item.children.reduce(filter, []), }); } else { return item; } } return prev; }, []); } console.log(recursiveFilter(input));

这是使用object-scan的不同类型的解决方案。

这个解决方案是迭代的而不是递归的。 它起作用是因为对象扫描以删除安全顺序迭代。 基本上我们遍历树并在任何匹配上中断。 然后我们跟踪不同深度的匹配(确保我们在遍历相邻分支时适当地重置该信息)。

这主要是一种学术练习,因为递归方法更清晰、更快。 但是,如果需要进行额外的处理或者堆栈深度错误是一个问题,那么这个答案可能会很有趣。

 // const objectScan = require('object-scan'); const myInput = [{ value: 'Miss1', children: [{ value: 'Miss2' }, { value: 'Hit1', children: [{ value: 'Miss3' }] }] }, { value: 'Miss4', children: [{ value: 'Miss5' }, { value: 'Miss6', children: [{ value: 'Hit2' }] }] }, { value: 'Miss7', children: [{ value: 'Miss8' }, { value: 'Miss9', children: [{ value: 'Miss10' }] }] }, { value: 'Hit3', children: [{ value: 'Miss11' }, { value: 'Miss12', children: [{ value: 'Miss13' }] }] }, { value: 'Miss14', children: [{ value: 'Hit4' }, { value: 'Miss15', children: [{ value: 'Miss16' }] }] }]; const myFilterFn = ({ value }) => value.includes('Hit'); const rewrite = (input, filterFn) => objectScan(['**(^children$)'], { breakFn: ({ isMatch, value }) => isMatch && filterFn(value), filterFn: ({ parent, property, key, value, context }) => { const len = (key.length - 1) / 2; if (context.prevLen <= len) { context.matches.length = context.prevLen + 1; } context.prevLen = len; if (context.matches[len + 1] === true || filterFn(value)) { context.matches[len] = true; return false; } parent.splice(property, 1); return true; }, useArraySelector: false, rtn: 'count' })(input, { prevLen: 0, matches: [] }); console.log(rewrite(myInput, myFilterFn)); // returns number of deletions // => 8 console.log(myInput); // => [ { value: 'Miss1', children: [ { value: 'Hit1', children: [ { value: 'Miss3' } ] } ] }, { value: 'Miss4', children: [ { value: 'Miss6', children: [ { value: 'Hit2' } ] } ] }, { value: 'Hit3', children: [ { value: 'Miss11' }, { value: 'Miss12', children: [ { value: 'Miss13' } ] } ] }, { value: 'Miss14', children: [ { value: 'Hit4' } ] } ]
 .as-console-wrapper {max-height: 100% !important; top: 0}
 <script src="https://bundle.run/object-scan@16.0.0"></script>

免责声明:我是对象扫描的作者

正在寻找另一种方法来解决这个问题而不直接改变 children 数组并想出了这个:

 const input = [ { value: 'Miss1', children: [ { value: 'Miss2' }, { value: 'Hit1', children: [ { value: 'Miss3' } ] } ] }, { value: 'Miss4', children: [ { value: 'Miss5' }, { value: 'Miss6', children: [ { value: 'Hit2' } ] } ] }, { value: 'Miss7', children: [ { value: 'Miss8' }, { value: 'Miss9', children: [ { value: 'Miss10' } ] } ] }, { value: 'Hit3', children: [ { value: 'Miss11' }, { value: 'Miss12', children: [ { value: 'Miss13' } ] } ] }, { value: 'Miss14', children: [ { value: 'Hit4' }, { value: 'Miss15', children: [ { value: 'Miss16' } ] } ] }, ]; const filtered = input.reduce(function fr(acc, curr) { if (curr.children) { const children = curr.children.reduce(fr, []); if (children.length) { return acc.concat({ ...curr, children: children }); } } if (curr.value.includes('Hit')) { return acc.concat(curr); } else { return acc; } }, []); console.log(filtered);

暂无
暂无

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

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