复杂的数组去重

复杂的数组去重

让我们先看一个简单的数组:

const cars = ['Mazda', 'Ford', 'Renault', 'Opel', 'Mazda'];

你可以看到,第一项和最后一项是相同的。在只有原始类型的数组中找到重复项是简单的。为了目的,我们可以简单使用 filter 方法和提供 indexof 回调函数。

const unique = cars.filter((car, idx) => cars.indexOf(car) === idx);
console.log(unique); // ['Mazda', 'Ford', 'Renault', 'Opel']

方法 indexOf 会找寻数组中的首先满足的项。这是为什么我们对比 indexOf 返回的索引确认是否是重复的项。

ES6 发布后,我们还可以使用 Set 配合扩展运算符或者 Array.from 来数组去重:

const cars = ['Mazda', 'Ford', 'Renault', 'Opel', 'Mazda'];

const uniqueWithSpreadOperator = [...new Set(cars)];
console.log(uniqueWithSpreadOperator); // outputs ["Mazda", "Ford", "Renault", "Opel"]

const uniqueWithArrayFrom = Array.from(new Set(cars));
console.log(uniqueWithArrayFrom); // outputs ["Mazda", "Ford", "Renault", "Opel"]

找寻重复对象

这是难点项。通过引用而不是值或结构来比较对象。这意味着如果我们比较两个完全相同的对象,它们将不匹配。我们不能简单地通过 obj1 === obj2 来比较它们。

const obj1 = {
  name: 'John',
  surname: 'Doe',
};

const obj2 = {
  name: 'John',
  surname: 'Doe',
};

const match = obj1 === obj2;
console.log(match); // false

现在,如果处理一个含有对象重复项的数组,我们怎么过滤它们呢?考虑我们刚刚读的内容,不可能简单地使用 indexOf。 数组例子:

const names = [
  {
    name: 'John',
    surname: 'Doe',
  },
  {
    name: 'Muhamed',
    surname: 'Ali',
  },
  {
    name: 'Mike',
    surname: 'Tyson',
  },
  {
    name: 'John',
    surname: 'Doe',
  },
  {
    name: 'John',
    surname: 'Doe',
  },
  {
    name: 'Mike',
    surname: 'Tyson',
  },
  {
    name: 'Mike',
    surname: 'Tyson',
  },
];

如你所见,我们有些重复项,让我们提供发现重复项的方法。

一个长版本

在这种方法中,我们将手动循环遍历源数组(方法 forEach),并使用该 find 方法检查结果数组中是否存在每个项。考虑到我们有一个对象数组,我们必须比较当前对象的每个属性,以确保项目是相同的。细分为步骤如下所示:

  • 获取对象属性
  • 定义结果数组(uniqueduplicates
  • 循环遍历源数组
  • 尝试在 unique 数组中找到当前项
  • 如果找到该项,请将其推 duplicatesunique 数组中
const findDuplicates = (source) => {
  const keys = Object.keys(source[0]);
  let unique = [],
    duplicates = [];

  source.forEach((item, idx) => {
    if (idx == 0) {
      unique.push(item);
      return;
    }

    const resultItem = unique.find((resultItem) => {
      let notFound = true;

      keys.forEach((key) => {
        notFound = notFound && item[key] != resultItem[key];
      });

      return !notFound;
    });

    (!resultItem ? unique : duplicates).push(item);
  });

  return { unique: unique, duplicates: duplicates };
};

const result = findDuplicates(names);
console.log(result.unique, result.duplicates);

// unique items
// 0: {name: "John", surname: "Doe"}
// 1: {name: "Muhamed", surname: "Ali"}
// 2: {name: "Mike", surname: "Tyson"}

// duplicate items
// 0: {name: "John", surname: "Doe"}
// 1: {name: "John", surname: "Doe"}
// 2: {name: "Mike", surname: "Tyson"}
// 3: {name: "Mike", surname: "Tyson"}

一个短版本

我们可以使用 reduce 方法实现相同的事情。这是一个非常有用的方法,来转换数组到想要的结果。它接收一个回调参数来处理数组的每一项。方法回调函数的返回值是每一个迭代的累加值。关于 reduce 的更多介绍请看 MDN 文档

👌,回到我们的代码,findDuplicates 方法的修改版本如下:

const findDuplicates = (source) => {
  const keys = Object.keys(source[0]);
  return source.reduce(
    (acc, item) => {
      const resultItem = acc.unique.find((x) => {
        let notFound = true;

        keys.forEach((key) => {
          notFound = notFound && item[key] != x[key];
        });

        return !notFound;
      });

      (!resultItem ? acc.unique : acc.duplicates).push(item);
      return acc;
    },
    {
      unique: [],
      duplicates: [],
    },
  );
};

该修改后的版本应当返回与之前相同的数组。

// unique items
// 0: {name: "John", surname: "Doe"}
// 1: {name: "Muhamed", surname: "Ali"}
// 2: {name: "Mike", surname: "Tyson"}

// duplicate items
// 0: {name: "John", surname: "Doe"}
// 1: {name: "John", surname: "Doe"}
// 2: {name: "Mike", surname: "Tyson"}
// 3: {name: "Mike", surname: "Tyson"}