由另一个对象数组筛选对象数组

22

我希望能够通过另一个对象数组来过滤对象数组。

我有两个对象数组,如下所示:

const array = [
    { id: 1, name: 'a1', sub: { id: 6, name: 'a1 sub' } },
    { id: 2, name: 'a2', sub: null },
    { id: 3, name: 'a3', sub: { id: 8, name: 'a3 sub' } },
    { id: 4, name: 'a4', sub: null },
    { id: 5, name: 'a5', sub: { id: 10, name: 'a5 sub' } },
];
const anotherArray = [
    { id: 1, name: 'a1', sub: { id: 6, name: 'a1 sub' } },
    { id: 2, name: 'a2', sub: null },
    { id: 5, name: 'a5', sub: { id: 10, name: 'a5 sub' } },
];

我希望筛选array中不在anotherArray中且包含子元素的项。

所以我期望的输出是:

[ { id: 3, name: 'a3', sub: { id: 8, name: 'a3 sub' } ]

注意:我已经用 for 循环做了这个,但它运行得太慢了。我想使用数组的 filter 方法来完成这个任务。

我用 for 循环写的代码如下:

for (let i = 0; i < array.length; i += 1) {
    let exist = false;
    const item = array[i];
    for (let j = 0; j < anotherArray.length; j += 1) {
      const anotherItem = anotherArray[j];
      if (item.id === anotherItem.id) {
        exist = true;
      }
    }
    if (item.sub && !exist) {
      this.newArray.push({
        text: `${item.sub.name} / ${item.name}`,
        value: item.id,
      });
    }
  }

2
Array#filter 也会在数组上迭代,但是在内部操作。天真地将 for 循环转换成 .filter 很可能不能使你的代码更快。请发布你的代码。 - Felix Kling
可能重复:https://stackoverflow.com/questions/49980942/filter-array-of-objects-with-another-array-of-object/49981599#49981599。请过滤一个对象数组,使用另一个对象数组中的值进行匹配。 - Naor Tedgi
5个回答

46

就像Felix提到的一样,Array#filter不会比本地for循环运行得更快,但是如果你真的想以函数式的方式使用它,这里有一个可能的解决方案:

const array = [
    { id: 1, name: 'a1', sub: { id: 6, name: 'a1 sub' } },
    { id: 2, name: 'a2', sub: null },
    { id: 3, name: 'a3', sub: { id: 8, name: 'a3 sub' } },
    { id: 4, name: 'a4', sub: null },
    { id: 5, name: 'a5', sub: { id: 10, name: 'a5 sub' } },
];

const anotherArray = [
    { id: 1, name: 'a1', sub: { id: 6, name: 'a1 sub' } },
    { id: 2, name: 'a2', sub: null },
    { id: 5, name: 'a5', sub: { id: 10, name: 'a5 sub' } },
];

const r = array.filter((elem) => !anotherArray.find(({ id }) => elem.id === id) && elem.sub);

console.log(r);


哦,谢谢,这很好用!我有300多个数组项,所以我认为for循环运行缓慢。 - arif
1
谢谢,这正是我想要的,只需要稍作修改。我有两个数组,需要在第三个数组中获取它们的差异。我只是将 "&&" 子部分删除了。+1 - Flinze

11
你可以使用Array.filter,然后使用Array.some,因为后者会返回布尔值,而不是像Array.find一样返回元素:

const a1 = [ { id: 1, name: 'a1', sub: { id: 6, name: 'a1 sub' } }, { id: 2, name: 'a2', sub: null }, { id: 3, name: 'a3', sub: { id: 8, name: 'a3 sub' } }, { id: 4, name: 'a4', sub: null }, { id: 5, name: 'a5', sub: { id: 10, name: 'a5 sub' } }, ]; 
const a2 = [ { id: 1, name: 'a1', sub: { id: 6, name: 'a1 sub' } }, { id: 2, name: 'a2', sub: null }, { id: 5, name: 'a5', sub: { id: 10, name: 'a5 sub' } }, ];

const result = a1.filter(({id, sub}) => !a2.some(x => x.id == id) && sub)

console.log(result)


2
你可以使用JSON.stringify来比较这两个对象。最好编写一个函数,递归地比较对象上的所有属性。

const array = [
    { id: 1, name: 'a1', sub: { id: 6, name: 'a1 sub' } },
    { id: 2, name: 'a2', sub: null },
    { id: 3, name: 'a3', sub: { id: 8, name: 'a3 sub' } },
    { id: 4, name: 'a4', sub: null },
    { id: 5, name: 'a5', sub: { id: 10, name: 'a5 sub' } },
];
const anotherArray = [
    { id: 1, name: 'a1', sub: { id: 6, name: 'a1 sub' } },
    { id: 2, name: 'a2', sub: null },
    { id: 5, name: 'a5', sub: { id: 10, name: 'a5 sub' } },
];

const notIn = (array1, array2) => array1.filter(item1 => {
    const item1Str = JSON.stringify(item1);
    return !array2.find(item2 => item1Str === JSON.stringify(item2))
  }
);

console.log(notIn(array, anotherArray));


JSON 的键值对是无序的,因此 JSON.stringify 不可靠。 - Mulan

1

好的,让我们一步一步解决这个问题。

为了简化流程,假设如果两个元素都有相同的id,则可以认为它们是相等的。

我会采用的第一种方法是遍历第一个数组,并且对于每个元素,遍历第二个数组以检查您定义的条件。

const A = [ /* ... */]
const B = [ /* ... */]

A.filter(el => {
  let existsInB = !!B.find(e => {
    return e.id === el.id
  }

  return existsInB && !!B.sub
})

如果我们确信当A和B具有相同的ID时,它们中的元素确实是相同的,那么我们可以跳过所有没有sub属性的A元素,以稍微提高性能。
A.filter(el => {
  if (!el.sub) return false

  let existsInB = !!B.find(e => {
    return e.id === el.id
  }

  return existsInB
})

现在,如果我们的数组比这个要大,那么这意味着我们浪费了很多时间在B中查找元素。 通常,在这种情况下,我会将要查找的数组转换为一个映射,就像这样。
var BMap = {}
B.forEach(el => {
  BMap[el.id] = el
})

A.filter(el => {
  if (!el.sub) return false

  return !!BMap[el.id]
})

这样做会在一开始浪费一点时间来创建地图,但之后你可以更快地找到你的元素。

从这里开始,还可以进行更多的优化,但我认为这已经足够回答这个问题了。


-1

优化版本

const array = [{
    id: 1,
    name: "a1",
    sub: {
      id: 6,
      name: "a1 sub"
    }
  },
  {
    id: 2,
    name: "a2",
    sub: null
  },
  {
    id: 3,
    name: "a3",
    sub: {
      id: 8,
      name: "a3 sub"
    }
  },
  {
    id: 4,
    name: "a4",
    sub: null
  },
  {
    id: 5,
    name: "a5",
    sub: {
      id: 10,
      name: "a5 sub"
    }
  },
];
const anotherArray = [{
    id: 1,
    name: "a1",
    sub: {
      id: 6,
      name: "a1 sub"
    }
  },
  {
    id: 2,
    name: "a2",
    sub: null
  },
  {
    id: 5,
    name: "a5",
    sub: {
      id: 10,
      name: "a5 sub"
    }
  },
];

const dict = anotherArray.reduce((acc, curr) => {
  const { id } = curr;
  acc[id] = curr;
  return acc;
}, {});

const result = array.filter((obj) => {
  const search = dict[obj.id];
  if (!search && obj.sub) return true;
  return false;
});

console.log(result);


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接