一起又双叒叕看数组去重—我有一千种方法让妳成为我的唯一
正所谓“一山不容二虎,一渊不藏两蛟”,在某些“矫情”的需求中,数组中不能存在重复的元素,于是就有了对数组去重方法的讨论,关于数组去重的方法由来已久,我当然也想不出什么原创的方法了,这里只是简单的总结一下以备忘。
不过,我们要先声明一下,上面的这句强行组成的谚语还有下一句叫做“除非一公一母” ;也就是说如果是一公一母的两个元素是可以同时存在的,为了避免混淆,这里规定元素之间的比较为严格相等,两个元素通过 === 比较返回 true 的视为相同元素,需要去重。接下来我们统一一下函数风格。函数名为
Deduplication
,接受参数为数组,返回的参数也为数组。在正式开始写代码之前,还需要明确一点就是数组去重不是找出数组中只出现一次的元素,而是让重复的元素有且仅出现一次。好了接下来一一列举我抄袭的数组去重方法,其实我还是修改了一些错误并且优化了一丁点的。
Idea1
双层循环,外层循环待去重数组,内层循环检查结果数组
如果在结果数组中有相同的值则跳过,不相同则push进结果数组
Solution1:
function
Deduplication(arr) {
var
result = [];
for
(
var
i = 0, alen = arr.length; i < alen; i++) {
var
item = arr[i];
for
(
var
j = 0, rlen= result.length; j < rlen; j++) {
if
(result[j] ===item)
break
;
}
if
(j === rlen)
//如果遍历完结果数组还没找到,说明不是重复的元素
result.push(item);
}
return
result;
}
var
arr=[2,8,6,
'2'
,5,6,4,5,8,4,6];
console.log(unique(arr));
//[ 2, 8, 6, '2', 5, 4 ]
当然如果不考虑兼容性的话,可以使用ES5新增加的数组迭代迭代方法和位置方法。
solution2:
function
Deduplication(arr) {
var
result = [];
arr.forEach (
function
(item,index,arr){
if
(result.indexOf(item)===-1){
result.push(item);
}
});
return
result;
}
var
arr=[2,8,6,
'2'
,5,6,4,5,8,4,6];
console.log(unique(arr));
//[ 2, 8, 6, '2', 5, 4 ]
更巧妙的方法是使用fliter来过滤原数组
solution3:
function
Deduplication(a) {
return
a.filter(
function
(item, index, array) {
return
array.indexOf(item) === index;
//indexOf方法只会返回元素第一次出现的位置,所以元素第一次出现时会是true,后面再出现就是false了
});
}
var
arr=[2,8,6,
'2'
,5,6,4,5,8,4,6];
console.log(Deduplication(arr));
//[ 2, 8, 6, '2', 5, 4 ]
Idea2
双层循环,外层循环待去重数组,内层循环检查外层循环当前项与其后面的所有的项
如果在当前项后面发现有相同的值,则跳过,否则push进结果数组
solution4:
function
Deduplication(arr) {
var
result = [];
for
(
var
i = 0,len=arr.length; i <len; i++) {
for
(
var
j = i + 1; j < len; j++){
if
(arr[i] === arr[j]){
break
;
//发现相同值就不需要循环了,而且后面的if判断语句也不会通过,相当于执行下次外层循环了
}
}
if
(j===len)
//未发现后面有相同值
result.push(arr[i]);
}
return
result;
}
var
arr=[2,8,6,
'2'
,5,6,4,5,8,4,6];
console.log(Deduplication(arr));
//[ 2, '2', 5, 8, 4, 6 ]
观察返回的数组会发现与前面的方法返回的不一样,但是还是出掉了重复的元素,仔细品读源代码会发现在发现当前项后面有重复项时,当前项并没有放入结果数组,而是继续循环,这样操作的结果是只有某个元素最后一次出现的位置才会被push进结果数组,而不像前面的方法是在元素第一次出现的时候就push进了数组。稍稍修改一下源代码,下面的源代码也是基于这种思想的。
solution5:
function
Deduplication(arr){
var
result = [];
for
(
var
i = 0,len=arr.length; i < len; i++){
for
(
var
j = i + 1; j < len; j++){
if
(arr[i] === arr[j]){
j=++i+1;
//如果当前项后面找到了重复元素,i自增一次并返回+1的值给j,相当于进入了下一次外循环
}
}
result.push(arr[i]);
}
return
result;
}
var
arr=[2,8,6,
'2'
,5,6,4,5,8,4,6];
console.log(Deduplication(arr));
//[ 2, '2', 5, 8, 4, 6 ]
前面的两种方法结果打乱了元素在原始数组中的顺序,如果支持ES5的话,则可以用下面的方法保持元素的原始顺序。
solution6:
function
Deduplication(){
var
result = [];
arr.forEach(
function
(item, index ,arr){
//这里利用map,filter方法也可以实现
//从传入参数的下一个索引值开始寻找是否存在重复
if
(arr.indexOf(item,index+1) === -1){
result.push(item);
}
})
return
result;
};
var
arr=[2,8,6,
'2'
,5,6,4,5,8,4,6];
console.log(Deduplication(arr));
//[ 2, 8, 6, '2', 5, 4 ]
Idea3
思路:
双层循环,外层循环元素,内层循环时比较值
值相同时,则删去这个值
solution7:
function
Deduplication(arr){
for
(
var
i = 0,len=arr.length; i < len; i++){
for
(j = i + 1; j < len; j++){
if
(arr[i] === arr[j]){
arr.splice(j,1);
//删除后面的重复元素,但是要注意的是删除一个元素过后,原数组的长度会发生变化所以len要-1,
//j也要减一,这是为了防止当前项后面出现两个相邻的重复元素的情况时,删掉前一个,后一个前移
len--;
j--;
}
}
}
return
arr;
};
var
arr=[2,8,6,
'2'
,5,6,4,5,8,4,6];
console.log(Deduplication(arr));
//[ 2, 8, 6, '2', 5, 4 ]
也许你可以注意到这里是直接通过数组的splice()方法直接在原数组上删除元素的,arr是引用类型,这里也可以不需要返回值。
solution8:
function
Deduplication(arr){
for
(
var
i = 0,len=arr.length; i < len; i++){
for
(j = i + 1; j < len; j++){
if
(arr[i] === arr[j]){
arr.splice(j,1);
//删除后面的重复元素,但是要注意的是删除一个元素过后,原数组的长度会发生变化所以len要-1,
//j也要减一,这是为了防止当前项后面出现两个相邻的重复元素的情况时,漏掉了后一个
len--;
j--;
}
}
}
};
var
arr=[2,8,6,
'2'
,5,6,4,5,8,4,6];
Deduplication(arr);
console.log(arr);
//[ 2, 8, 6, '2', 5, 4 ]
Idea4
思路:对象的不能有两个相同的属性,就像哈希表一样
准备一个空的结果数组和一个空对象,循环遍历数组
如果当前数组元素已经是对象的属性则跳过,否则,将此元素作为对象的键,值可以为任意有效值如true,并将此元素push进结果数组
solution9:
function
Deduplication(arr){
var
obj = {},result = [];
for
(
var
i = 0,len=arr.length; i<len; i++){
if
(!obj[arr[i]]){
//如果能查找到,证明数组元素重复了
obj[arr[i]] =
true
;
result.push(arr[i]);
}
}
return
result;
};
var
arr=[2,8,6,
'2'
,5,6,4,5,8,4,6];
console.log(Deduplication(arr));
//[ 2, 8, 6, 5, 4 ]
当然如果支持ES5的话,上面的思想还可以简化为:
solution10:
function
Deduplication(arr) {
var
obj = {};
return
arr.filter(
function
(item) {
return
obj.hasOwnProperty(item) ?
false
: (obj[item] =
true
);
});
}
var
arr=[2,8,6,
'2'
,5,6,4,5,8,4,6];
console.log(Deduplication(arr));
//[ 2, 8, 6, 5, 4 ]
细心的小伙伴有没有发现一个问题,数组中的字符串'2'并没有出现在结果数组中,这是因为JavaScript中对象的属性都是字符串,如果不是会自动转化,这样以来数字2和字符串'2',对应的属性是相同的,字符串'2'就被当做重复的元素过滤掉了,所以这种方法比较适合于数组元素都是数字或者字符串的情况。不过我们仍然可以通过一些小技巧解决这个问题,可以把元素的数据类型也作为对象属性的一部分。
solution11:
function
Deduplication(arr){
var
obj = {},result = [];
for
(
var
i = 0,len=arr.length; i<len; i++){
var
item=arr[i];
var
key=
typeof
(item)+item;
if
(!obj[key]){
//如果能查找到,证明数组元素重复了
obj[key] =
true
;
result.push(arr[i]);
}
}
return
result;
};
var
arr=[2,8,6,
'2'
,5,6,4,5,8,4,6];
console.log(Deduplication(arr));
//[ 2, 8, 6, '2', 5, 4 ]
Idea5
思路:排序后再删除重复元素(so这个方法会打乱数组元素原来的顺序)
排序后的重复元素会变成相邻元素
比较相邻元素,删除重复值
solution12:
function
Deduplication(arr){
arr.sort();
//注意这个地方的concat:返回数组的副本并排序
for
(
var
i =0,len=arr.length; i <len; i++) {
if
(arr[i]===arr[i+1]) {
arr.splice(i,1);
len--;
i--;
}
}
return
arr;
};
var
arr=[2,8,6,'2',5,6,4,5,8,4,6];
console.log(Deduplication(arr));
//[ 2, '2', 4, 5, 6, 8 ]
如果可以使用ES5特性的话,可以使用一下几种变形
solution13:
function
Deduplication(arr){
var
newarr = arr.concat().sort();
//注意这个地方的concat:返回数组的副本并排序
newarr.sort(
function
(a,b){
//再对副本数组进行排序(相邻的两个元素进行比较)时,操作原有数组arr
if
(a === b){
var
index = arr.indexOf(a);
arr.splice(index,1);
}
});
return
arr;
};
var
arr=[2,8,6,'2',5,6,4,5,8,4,6];
console.log(Deduplication(arr));
//[ 2, '2', 4, 5, 6, 8 ]
solution14:
function
Deduplication(arr){
//如果不想破坏原数组可以使用arr.concat()先创建副本
return
arr.sort().filter(
function
(item, pos, arr) {
return
item !== arr[pos+1];
});
};
var
arr=[2,8,6,'2',5,6,4,5,8,4,6];
console.log(Deduplication(arr));
//[ 2, '2', 4, 5, 6, 8 ]
Idea5有一个共性问题就是数组sort()方法是默认按字符ASCII排序的(如果是数字会转化为字符),使用时要注意这一点。例如数字2和字符'2'会排在一起,看下面的例子。
function
Deduplication(arr){
//如果不想破坏原数组可以使用arr.concat()先创建副本
return
arr.sort().filter(
function
(item, pos, arr) {
return
item !== arr[pos+1];
});
};
var
arr=[2,8,6,
'2'
,2,5,6,4,5,8,4,6];
console.log(Deduplication(arr));
//[ 2, '2', 2, 4, 5, 6, 8 ]
结果中出现了重复的2,那是因为前后比较时字符串'2'使得前后不相等。当然解决这个问题可以借鉴前面的方法,加入数据类型判断。
Idea6
ES6大法好:
思路:利用Array.from将Set结构转换成数组
solution15
function
Deduplication(arr){
return
Array.from(
new
Set(arr));
}
var
arr=[2,8,6,
'2'
,2,5,6,4,5,8,4,6];
console.log(Deduplication(arr));
//[ 2, 8, 6, '2', 5, 4 ]
参考:
也谈面试必备问题之 JavaScript 数组去重再见,重复的你(数组去重)