递归过滤对象数组

IT技术 javascript arrays recursion filter
2021-01-25 07:48:13

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

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

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

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

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

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

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

输出应如下所示:

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

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

6个回答

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

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

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


请注意,.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
})

虽然读起来有点困难。

@guest271314:我想你可能会像我最初那样思考它,即使某物有一个“命中”的后代,它的所有后代也会被保留。这实际上也很容易,但从描述和显示的输出来看,唯一保留的未命中要么是“命中”的后代,要么是“命中”的祖先。
2021-03-17 07:48:13
@squintchildren如果children数组不包含前面对象包含的"Hit"位置value是否要求在结果中包含数组例如,过滤数组索引处项目value"Hit"3
2021-03-20 07:48:13
最简单的方法是从一开始就克隆整个结构。如果每个对象确实只有 2 个属性,那么在每个对象.map()上先做一个.filter()应该足够简单了。
2021-03-23 07:48:13
它改变了原来的内容input您可能首先想使用JSON.parse(JSON.stringify(input)). 另外,我觉得这是服务器端 JS。
2021-03-26 07:48:13
你能解释一下为什么length需要return (o.children = o.children.filter(f)).length吗?
2021-04-13 07:48:13

这是一个可以完成您正在寻找的功能的函数。本质上,它将测试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;
}
谢谢,这种方法实际上让我更清楚我哪里出错了
2021-04-02 07:48:13
我认为将其称为“过滤器”是一个但令人困惑的方法,因为“过滤器”已经是 Javascript 中的一个数组方法。我们可以称之为“搜索”
2021-04-06 07:48:13

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

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)

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

这是一个很好的解决方案,它利用数组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));

@mailk:它仍然不起作用。)第 3 行有一个缺失,但即使修复了它,您仍然有一个未定义的route变量。请在编写 JS/HTML/CSS 解决方案时,如果可能的话,创建一个片段来演示它的工作原理。
2021-03-18 07:48:13
请仅在实际解决手头问题的情况下提交其他问题的现有解决方案;这个问题与routes 或无关isAuthorized.如果有类比,请制作它们。此外,您的简要介绍还不足以阻止它成为纯代码解决方案;不要张贴那些。最后,当复活一个旧问题时,请确保您有新的内容要添加。
2021-03-24 07:48:13
@ScottSauyet 抱歉……添加了必要的更改。它似乎工作正常
2021-03-26 07:48:13
@ScottSauyet 抱歉缺乏上下文,我添加了必要的更改,我觉得与其他解决方案相比,这个特定的解决方案更具可读性和合理性。
2021-04-10 07:48:13
@mailk:问题中有请求的输出。这不匹配,甚至不接近。接受的答案和这里的其他几个答案都是如此。我认为如果你要迟到,你真的应该得到一个符合要求的解决方案。
2021-04-10 07:48:13