数组去重是非常经典的问题了,不同的实现方法,性能要求差别非常大。
假设有这样的数组
去掉重复的项,只保留一个
let arr = [0, 1, 2, 2, 3, 3, 3, 4];
let filterArr = unique(arr)
filterArr => [1,1,1,2,2,2,3,3];
使用双for循环遍历,大概思路。外层循环从数组第0项开始遍历,内层循环从第i+1项开始遍历到数组最后的位置,如果有重复的项就删除。
const unique = (arr) => {
for (var i = 0; i < arr.length - 1; i++) {
for (var j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j]) {
arr.splice(j, 1);
j--; // 改变了数组长度以后让循环留在原位
}
}
return arr;
}
这里要特别注意的是j–这个操作,因为splice删除数组某一项会导致数组挪位,如果出现连续重复的项,就会出现问题。
该方法非常消耗性能,时间复杂度为O(n^2)
2.通过对象属性名唯一去重
思路是对象的属性名唯一,重复的属性名会被覆盖,遍历数组的每一项,以对象属性名的方式保存,如果已经存在就删去。
const unique = (arr) => {
let obj = {};
for (let i = 0; i < arr.length; i++){
if (typeof obj[arr[i] !== 'undefined') { // 数组中已经出现过
arr.splice(i, 1);
i--;
continue;
}
// 第一次出现就保存到对象中
obj[arr[i]] = arr[i];
}
return arr;
}
这种方式相对比较节省性能,但是有缺陷,想象一下如果属性名为’1’和1,不论哪一种都会覆盖彼此。
意思是:
如果数组长这样
let arr = [0, 1, '1', '1', 2];
console.log(unique(arr)); //=> [0, 1, 2]
因为不论是数字类型还是字符串类型,最终都以字符串形式保存入对象。
const unique = (arr) => {
// 考虑空数组,若非空则放入第一项到新数组
var item = typeof arr[0] === 'undefined' ? [] : [arr[0]];
for (var i = 1; i < arr.length; i++) {
// 如果不存在就将该项推入数组,存在就不继续执行
item.indexOf(arr[i]) === -1 && item.push(arr[i])
}
return item;
}
核心思想:利用indexOf()在新数组中查找,如果不存在返回-1的特性。
利用indexOf另外一个特性,返回数组中该值第一次出现的索引值,利用forEach每次循环的index匹配,如果是第一次出现的就压入新数组。
const unique = (arr) => {
let item = [];
arr.forEach((e, index) => {
if(index === arr.indexOf(e)) {
item.push(e);
}
})
return item;
}
但是indexOf的内部实现原理也是for循环,所以跟双for循环类似,都非常消耗性能。
es6也提供了一些方法更加简洁。
以上我们知道Set中的元素是唯一的,也就是其包含的每个value是唯一的
const unique = (arr) => [...new Set(arr)];
// or
const unique = (arr) => Array.from(new Set(arr));
Array.from()
方法就是将一个类数组对象或者可遍历对象转换成一个真正的数组,个人更喜欢展开运算符。
如果有更好的方法再来补充~