你的位置:首页 > Java教程

[Java教程]一起又双叒叕看数组去重—我有一千种方法让妳成为我的唯一


  正所谓“一山不容二虎,一渊不藏两蛟”,在某些“矫情”的需求中,数组中不能存在重复的元素,于是就有了对数组去重方法的讨论,关于数组去重的方法由来已久,我当然也想不出什么原创的方法了,这里只是简单的总结一下以备忘。

  不过,我们要先声明一下,上面的这句强行组成的谚语还有下一句叫做“除非一公一母” ;也就是说如果是一公一母的两个元素是可以同时存在的,为了避免混淆,这里规定元素之间的比较为严格相等,两个元素通过 === 比较返回 true 的视为相同元素,需要去重。接下来我们统一一下函数风格。函数名为Deduplication ,接受参数为数组,返回的参数也为数组。在正式开始写代码之前,还需要明确一点就是数组去重不是找出数组中只出现一次的元素,而是让重复的元素有且仅出现一次。好了接下来一一列举我抄袭的数组去重方法,其实我还是修改了一些错误并且优化了一丁点的。

Idea1

思路:

 

  1. 双层循环,外层循环待去重数组,内层循环检查结果数组

  2. 如果在结果数组中有相同的值则跳过,不相同则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

思路:

 

  1. 双层循环,外层循环待去重数组,内层循环检查外层循环当前项与其后面的所有的项

  2. 如果在当前项后面发现有相同的值,则跳过,否则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

思路:

  1. 双层循环,外层循环元素,内层循环时比较值

  2. 值相同时,则删去这个值

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

 思路:对象的不能有两个相同的属性,就像哈希表一样

 

  1. 准备一个空的结果数组和一个空对象,循环遍历数组

  2. 如果当前数组元素已经是对象的属性则跳过,否则,将此元素作为对象的键,值可以为任意有效值如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这个方法会打乱数组元素原来的顺序)

 

  1. 排序后的重复元素会变成相邻元素

  2. 比较相邻元素,删除重复值

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 数组去重
再见,重复的你(数组去重)

 

 

 

 

 

 



来自为知笔记(Wiz)