zoukankan      html  css  js  c++  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 数组去重
    再见,重复的你(数组去重)










  • 相关阅读:
    课堂练习
    软件工程课堂练习二维数组子数组和最大值,只要连续就好
    结对项目电梯调度
    第二次课堂练习
    软件工程课堂练习二维数组子数组和的最大值
    敏捷开发方法
    软件工程个人作业
    电梯调度
    创意
    02合并frame
  • 原文地址:https://www.cnblogs.com/star91/p/5633287.html
Copyright © 2011-2022 走看看