五种数组去重方法的性能比较

3,244 阅读3分钟

为什么要写这篇文章

之前参与面试一名外包程序员的时候,我要求他手写一个数组去重的方法,当时他使用了对象保存数值,通过查询去重。

我表示表占用了空间,能不能只操作数组本身,减少空间占用。

当时我想的是用 indexOf 和 splice 来操作数组,查询到 index 不等于 i 的时候,使用 splice 删除元素,之后自己跑了一遍发现函数执行时间非常长,所以想着研究一下效率比较高的去重方法。

去重方法介绍

外包同学写的表查询去重

// 
function unique1(arr) {
  let hash = {};
  let result = [];
  arr.forEach((num) => {
    if (!hash[num]) {
        hash[num] = 1;
        result.push(num);
    }
  });
  return result;
}

思路:如果遍历到的数字不在表中,则更新表,更新结果数组。否则跳过

这个方法的优点是效率较高,缺点是占用了较多空间,可以看到使用的额外空间有一个查询对象和一个新的数组

ES6 的 Set 去重

function unique2(arr) {
  return Array.from(new Set(arr));
}

思路: 代码很简单,new 一个 Set,参数为需要去重的数组,Set 会自动删除重复的元素,再将 Set 转为数组返回。

这个方法的优点是效率更高,代码简单,思路清晰,缺点是可能会有兼容性问题

使用 filter 去重

function unique3(arr) {
  return arr.filter((num, index) => {
    return arr.indexOf(num) === index;
  });
}

思路: 这个方法与我上面面试时提到的思路差不多,利用 Array 自带的 filter 方法,返回 arr.indexOf(num) 等于 index 的num。原理就是 indexOf 会返回最先找到的数字的索引,假设数组是 [1, 1],在对第二个1使用 indexOf 方法时,返回的是第一个1的索引0。

这个方法的优点是可以在去重的时候插入对元素的操作,可拓展性强。

数组内部移动后返回部分数组

function unique4(arr) {
  let right = arr.length - 1;
  for (let i = 0; i < arr.length; i++) {
    if (right <= i) {
      break;
    }
    if (arr.indexOf(arr[i]) !== i) {
      [arr[i], arr[right]] = [arr[right], arr[i]];
      right--;
      i--;
    }
  }
  return arr.slice(0, right);
}

思路:这个方法比较巧妙,从头遍历数组,如果元素在前面出现过,则将当前元素挪到最后面,继续遍历,直到遍历完所有元素,之后将那些被挪到后面的元素抛弃。

这个方法因为是直接操作数组,占用内存较少。

使用 reduce 去重

function unique5(arr) {
  return arr.reduce((pre, cur) => {
    if (pre.indexOf(cur) === -1) {
      pre.push(cur);
    }
    return pre;
  }, []);
}

思路:reduce 是 Array 的一个迭代方法,参数为:迭代函数(参数为上一次回调的返回值(pre),当前遍历的元素(cur)),初始值。 在本例中传入一个空数组作为初始值,每次迭代时如果发现 pre 中没有当前元素,就把当前元素推入 pre,然后返回 pre 结束本次迭代。

这个方法花里胡哨的。

性能比较

随机生成五个数组,长度分别为10,100,1000,10000,100000,数值范围分别为0-10,0-100,0-1000,0-10000,0-100000。 分别使用这五个方法对五个数组进行去重后查看执行方法所用的时间。

由图可知,方法1和方法2消耗时间最短,然后依次是3,4,5。处理数据较少时(10000以下)性能是差不多的。

而方法1和方法2中,方法1消耗较多空间,所以建议使用方法2也就是 Set。