• JavaScript---数组去重


     1、双层循环

    方法一:

    var unique = (arr)=>{
              for(let i = 0; i < arr.length; i++){
                  for(let j = i + 1; j < arr.length; j++){
                      if(arr[i] === arr[j]){
                          arr.splice(j, 1); // 移除重复元素
                          j--; // 修正下标
                      }
                  }
              }
              return arr;
          }
    unique([1, '1', '1', 1, 2, true, false, true, 3, 2, 2, 1]); // [ 1, '1', 2, true, false, 3 ]

    方法二:

    const unique = (arr)=>{
        var arr = [1, '1', '1', 1, 2, true, false, true, 3, 2, 2, 1];
        var newArr = [];
        for(let i = 0; i < arr.length; i++){
            for(var j = 0; j < newArr.length; j++){ // 注意var声明的j
                if(arr[i] === newArr[j]) break; // 新数组已存在当前值
            }
            if(j === newArr.length){ // 此时j和newArr长度相等,没有被中断
                newArr.push(arr[i]);
            }
        }
        return newArr;
    } 

    小结:

    • 时间复杂度:O(n^2)
    • 以上两种方法都是双层循环,只是处理方法不同
    • 双层循环的方法不是最佳选择,但兼容性好。

    2、indexOf和includes

    ①indexOf简化一层循环判断

    • 如果需要返回原数组,则可以在indexOf方法找到重复项时(不等于它首次出现的位置)时利用splice移除
    • indexOf:返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。
    • indexOf(ele, fromIndex)
      • ele:要查找的元素
      • fromIndex:查找元素的起始位置,默认为0,允许负数,-2表示从倒数第二个元素开始查找
      • 返回一个下标(number)
    const unique = (arr) => {
        var res = [];
        for (let i = 0; i < arr.length; i++){
            if(res.indexOf(arr[i]) === -1 ){
                res.push(arr[i]);
            }
        }
        return res;
    }
    

      

    ②includes简化一层循环判断

    • 具体的是要返回原数组还是新数组大家可以自行组合~
    • includes:用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false
    • includes(ele, fromIndex)
      • ele:要查找的元素
      • fromIndex:指定索引处开始查找。默认为 0,如果为负值,从末尾开始往前跳 fromIndex 的绝对值个索引。。
      • 返回结果(bool)
    const unique = (arr) => {
        var res = [];
        for (let i = 0; i < arr.length; i++){
            if(!res.includes(arr[i])){
                res.push(arr[i]);
            }
        }
        return res;
    }
    

      场景选择:

    • 这里推荐使用includes
    if(res.indexOf(arr[i]) !== -1 ){ todo }
    // or
    if(res.includes(arr[i])){ todo }
    • 识别NaN   -----使用includes

     如果数组中有NaN,你又正好需要判断数组是否有存在NaN,这时你使用indexOf是无法判断的,你必须使用includes这个方法。

    var arr = [NaN, NaN];
    arr.indexOf(NaN); // -1
    arr.includes(NaN); // true
    • 识别undefined   -----使用indexOf

    如果数组中有undefined值,includes会认为空的值是undefined,而indexOf不会。

    var arr = new Array(3);
    console.log(arr.indexOf(undefined)); //-1
    console.log(arr.includes(undefined)) //true
    

      3、排序去重

    • 数组排序后,相同的元素会相邻,所以如果当前元素与它的相邻元素不同,就存入到新数组中;
    • 相比于indexOf,只需要一层循环;
    • concat会拼接数组,并返回新数组;
    • sort()排序是通过按照转换为的字符串的各个字符的Unicode位点进行排序。所以很难保证它的准确性;
    var arr = [1, 1, '1'];
    function unique(arr) {
        var res = [];
        var sortedArr = arr.concat().sort();
        var last;
        for (var i = 0; i < sortedArr.length ; i++) {
            // 如果是第一个元素或者相邻的元素不相同
            if (!i || last !== sortedArr[i]) {
                res.push(sortedArr[i])
            }
            last = sortedArr[i]; // 记录上一个值
        }
        return res;
    }
    console.log(unique(array));
    

      4、filter

    • filter:方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素(返回测试函数成立的元素)
    • filter(callback, thisArg)
      • callback接受三个参数:element-当前正在处理的元素,index-当前元素索引,array-调用了filter的数组本身
      • thisArg:执行 callback 时,用于 this 的值。

          利用filter我们可以在代码层面简化一下外层循环:

    var arr = [1, 2, 1, 1, '1'];
    
    const unique = function (arr) {
        var res = arr.filter(function(item, index, arr){
            return arr.indexOf(item) === index;
        })
        return res;
    }
    console.log(unique(arr)); // [ 1, 2, '1' ]
    

      结合排序的思路:

    var arr = [1, 2, 1, 1, '1'];
    
    const unique = function (arr) {
        return arr.concat().sort().filter(function(item, index, arr){
            return !index || item !== arr[index - 1]
        })
    }
    
    console.log(unique(arr));
    

     5、键值对(key-value)

    前面提到的几种方式总结下来大致分为

    1. 非排序数组,两次遍历判断(遍历、查询)
    2. 排序数组,相邻元素比较

    我们再提出一种方式,利用Object对象 key-value的方式,来统计数组中元素出现的个数,初步判断逻辑也有两种

    [1,1,1,2,2,3,'3']举例:

    1. 统计每个元素出现的次数,obj:{1: 3, 2: 2, 3: 3}, 返回这个objkey而不管他们的value
    2. 只元素首次出现,再次出现则证明他是重复元素
    5.1 统计次数
    var arr = [1, 2, 1, 1, '1', 3, 3];
    
    const unique = function(arr) {
        var obj = {};
        var res = [];
        arr.forEach(item => {
            if (!obj[item]) {
                obj[item] = true;
                res.push(item);
            }
        });
        return res;
    }
    console.log(unique(arr)); // [1, 2, 3]
    5.2 结合filter
    var arr = [1, 2, 1, 1, '1'];
    
    const unique = function(arr) {
        var obj = {};
        return arr.filter(function(item, index, arr){
            return obj.hasOwnProperty(item) ? false : (obj[item] = true)
        })
    }
    console.log(unique(arr)); // [1, 2]
    
    5.3 key: value存在的问题

    对象的属性是字符串类型的,即本身数字1字符串‘1’是不同的,但保存到对象中时会发生隐式类型转换,导致去重存在一定的隐患。

    考虑到string和number的区别(typeof 1 === ‘number’, typeof ‘1’ === ‘string’),

    所以我们可以使用 typeof item + item 拼成字符串作为 key 值来避免这个问题:

    var arr = [1, 2, 1, 1, '1', 3, 3, '2'];
    
    const unique = function(arr) {
        var obj = {};
        var res = [];
        arr.forEach(item => {
            if (!obj[typeof item + item]) {
                obj[typeof item + item] = true;
                res.push(item);
            }
        });
        return res;
    }
    console.log(unique(arr)); // [ 1, 2, '1', 3, '2' ]
    

      6、ES6

    随着 ES6 的到来,去重的方法又有了进展,比如我们可以使用 Set 和 Map 数据结构。

    6.1 Set

    Set:它允许你存储任何类型的唯一值,无论是原始值或者是对象引用

    代码:

    var arr = [1, 2, 1, '1', '2'];
    
    const unique = function(arr) {
       return Array.from(new Set(arr));
    }
    console.log(unique(arr)); // [ 1, 2, '1', '2' ]
    

      简化1:

    function unique(array) {
        return [...new Set(array)];
    }
    

      简化2:

    var unique = (a) => [...new Set(a)]
    6.2 Map

    Map 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者原始值) 都可以作为一个键或一个值。

    • Map.prototype.has(key):返回一个布尔值,表示Map实例是否包含键对应的值。
    • Map.prototype.set(key, value):设置Map对象中键的值。返回该Map对象。
    function unique (arr) {
        const newMap = new Map()
        return arr.filter((a) => !newMap.has(a) && newMap.set(a, 1));
    }
    

      写到这里比较常规的数组去重方法就总结的差不多了,如果需要更强大的去重方法,我们需要对他们进行组合,而且因为场景的不同,我们所实现的方法并不一定能涵盖到

    问题:

    上面几个去重的方法,在针对数字、字符串、特殊字符、undefined、NaN的表现各不相同。

    1 === 1 // true
    undefined === undefined // true
    'str' === 'str' // true
    1 === '1' // false
    NaN === NaN // false
    

     最后整理:

    var array = [1, 1, '1', '1', null, null, undefined, undefined, new String('1'), new String('1'), /a/, /a/, NaN, NaN];
    

      以上各种方法去重的结果到底是什么样的呢?

      以下为重点专注对象和NaN的去重情况:

    方法结果说明
    for循环 [1, "1", null, undefined, String, String, /a/, /a/, NaN, NaN] 对象和 NaN 不去重
    indexOf [1, "1", null, undefined, String, String, /a/, /a/, NaN, NaN] 对象和 NaN 不去重
    sort [/a/, /a/, "1", 1, String, 1, String, NaN, NaN, null, undefined] 对象和 NaN 不去重 数字 1 也不去重
    filter + indexOf [1, "1", null, undefined, String, String, /a/, /a/] 对象不去重 NaN 会被忽略掉
    filter + sort [/a/, /a/, "1", 1, String, 1, String, NaN, NaN, null, undefined] 对象和 NaN 不去重 数字 1 不去重
    优化后的键值对方法 [1, "1", null, undefined, String, /a/, NaN] 全部去重
    Set [1, "1", null, undefined, String, String, /a/, /a/, NaN] 对象不去重 NaN 去重
  • 相关阅读:
    默哀STAND SILENTLY!
    用虚拟机优化Windows(update:2008.4.24)
    UE的心情指数?
    God of War III 的发售日期?
    2009/8/15应该是一个愉快的夜晚.为林肯公园中国10月演唱会做好准备
    北京2008奥运会完美谢幕!
    《The Pursuit of Happyness / 当幸福来敲门》(2006)
    2007林肯公园上海演唱会观后感(实况像片/MP3) update:2008.1.31
    2008早上好
    Active Object C++智能指针实现
  • 原文地址:https://www.cnblogs.com/meiyanstar/p/13913487.html
Copyright © 2020-2023  润新知