数组去重

34 阅读1分钟

面试遇到问数组去重,一开始我以为是简单的数组去重,后来面试官追问到其它极限情况呢?或者其它数据结构,所以简单研究了一下数据去重的情况,路过的大佬们指导指导。

数字数组和字符串数组

直接使用Set最优雅最高效,如果数组内包含undefined、null、""、0等临界值也会被去重。

const arr = [1, undefined, null, null, 2, 3, undefined, 0, 0, '', '','1','1','2','2','3'];
const result = Array.from(new Set(arr));

console.log(result); // [1, undefined, null, 2, 3, 0, '', '1', '2', '3']

对象数组

// 下面这种方法优雅且高效,但是会用后面的数据覆盖前面的数据,且对undefined、null、{}没有做处理
const arr = [
    { id: 1, name: 'A' },
    { id: 2, name: 'B' },
    { id: 1, name: 'C' },
];
const result = Array.from(
  new Map(arr.map((item) => [`${item.id}`, item])).values()
);
console.log(result); // [{id: 1, name: 'C'},{id: 2, name: 'B'}]

// 下面这种方法更加推荐
const result = [];
const seen = new Set();

// 如果想实现上面的效果只需要去掉!seen.has(item.id)这段代码即可
for (const item of arr) {
  if (item && Object.hasOwn(item, 'id') && !seen.has(item.id)) {
    seen.add(item.id);
    result.push(item);
  }
}

以上是比较常见的数据结构,下面为不太常见的复杂数据结构。


多维数组

:::info 多维数据内的数据复杂程度非常高,且一般伴随着大数据量这一特性一起出现,下面将尽可能的展开说说不同情况的个人理解。

:::

二维数组

const arr = [
  [1, 2],
  [3, 4],
  [1, 2],
  [3, 4, 5],
  [3, 4],
  [],
  [],
  [undefined, 1],
  [undefined],
  ['', 0],
  ['', 0],
  ['', undefined],
];
const result = [];
const seen = new Set();
for (const item of arr) {
  const key = item.join('|'); // 用分隔符拼接
  if (!seen.has(key)) {
    seen.add(key);
    result.push(item);
  }
}
// [1, 2],[3, 4],[3, 4, 5],[],[undefined, 1],[undefined],['', 0],['', undefined]

// 下面这种用map的方法,性能略差,但是自由度很高,可以通过key和item控制内容和去重的判断内容
const map = new Map();
for (const item of arr) {
  const key = JSON.stringify(item);
  if (!map.has(key)) {
    map.set(key, item);
  }
}
console.log(map.values());
// [1, 2],[3, 4],[3, 4, 5],[],[undefined, 1],[undefined],['', 0],['', undefined]

三维及以上数组

const arr = [
    [1, [2, 3]],
    [1, [2, 3]],
    [[]],
    [[]],
    [undefined],
    [undefined],
    [null],
    [0],
    [0],
    [[0],0],
    [[0],undefined],
  ];
// 用自定义的hash算法
function deepHash(value) {
  const type = typeof value;

  if (value === null) return "null";
  if (type === "number" || type === "boolean") return value + "";
  if (type === "string") return `"${value}"`;

  if (Array.isArray(value)) {
    let hash = "A"; // A = array
    for (let i = 0; i < value.length; i++) {
      hash += "|" + deepHash(value[i]);
    }
    return hash;
  }

  // 如果未来有对象,也支持
  if (type === "object") {
    let hash = "O";
    const keys = Object.keys(value).sort(); // 避免乱序
    for (const k of keys) {
      hash += "|" + k + ":" + deepHash(value[k]);
    }
    return hash;
  }
}

function deepUnique(arr) {
  const seen = new Set();
  const result = [];

  for (const item of arr) {
    const key = deepHash(item); // 非 JSON
    const key = JSON.stringify(item); // 使用JSON字符串
    if (!seen.has(key)) {
      seen.add(key);
      result.push(item);
    }
  }

  return result;
}
const temp = deepUnique(arr);
console.log(temp);
// [[1,[2,3]],[[]],[null],[null],[0],[[0],0],[[0],null]]

树状对象数组

// 全局去重
function dedupeTree(tree, getKey) {
  const seen = new Set();

  function traverse(nodes) {
    const result = [];

    for (const node of nodes) {
      const key = getKey(node);
      if (seen.has(key)) continue;

      seen.add(key);

      const newNode = { ...node };

      if (Array.isArray(newNode.children)) {
        newNode.children = traverse(newNode.children);
      }

      result.push(newNode);
    }

    return result;
  }

  return traverse(tree);
}
// 层级去重
function dedupeTreeLevel(nodes, key = "id") {
  const seen = new Set();
  const result = [];

  for (const node of nodes) {
    if (!seen.has(node[key])) {
      seen.add(node[key]);

      const newNode = { ...node };
      if (newNode.children) {
        newNode.children = dedupeTreeLevel(newNode.children, key);
      }

      result.push(newNode);
    }
  }

  return result;
}