一起又双叒叕看数组去重—小弟我有一千种方法让妳成为小弟我的唯一
正所谓“一山不容二虎,一渊不藏两蛟”,在某些“矫情”的需求中,数组中不能存在重复的元素,于是就有了对数组去重方法的讨论,关于数组去重的方法由来已久,我当然也想不出什么原创的方法了,这里只是简单的总结一下以备忘。
不过,我们要先声明一下,上面的这句强行组成的谚语还有下一句叫做“除非一公一母” ;也就是说如果是一公一母的两个元素是可以同时存在的,为了避免混淆,这里规定元素之间的比较为严格相等,两个元素通过 === 比较返回 true 的视为相同元素,需要去重。接下来我们统一一下函数风格。函数名为
Deduplication
,接受参数为数组,返回的参数也为数组。在正式开始写代码之前,还需要明确一点就是数组去重不是找出数组中只出现一次的元素,而是让重复的元素有且仅出现一次。好了接下来一一列举我抄袭的数组去重方法,其实我还是修改了一些错误并且优化了一丁点的。
Idea1
-
双层循环,外层循环待去重数组,内层循环检查结果数组
-
如果在结果数组中有相同的值则跳过,不相同则push进结果数组
Solution1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
|
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:
1
2
3
4
5
6
7
8
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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也要减一,这是为了防止当前项后面出现两个相邻的重复元素的情况时,删掉前一个,后一个前移
//下次内循环时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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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:
1
2
3
4
5
6
7
8
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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:
1
2
3
4
5
6
7
8
|
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'会排在一起,看下面的例子。
1
2
3
4
5
6
7
8
|
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
1
2
3
4
5
|
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 数组去重
再见,重复的你(数组去重)
- 1楼贾顺名
- Array.prototype.clear = function() {,return this.filter(function(item, idx) {,return this.indexOf(item) === idx;