回味基础:数组去重

263 阅读2分钟

数组去重是一个特别常见的问题,大多时候也能想起一两种常用的方式来解决,但是具体有多少种方式呢,好想还没试过。

以下面这个数组为例,写一个unique的函数来进行去重处理。

var arr = [1,1,'1', '1', '', '', true,'true',true,false,false,'false',undefined,'undefined',undefined,null,'null',null, {}, {}, [], [], NaN, NaN];

unique(arr);

for + indexOf 循环:

不能对对象和NaN去重

function unique(arr) {
    var tempArr = [];
    var len = arr.length;
    for(var i = 0; i<len; i++) {
        if(tempArr.indexOf(arr[i]) < 0) {
            tempArr.push(arr[i]);
        }
    }
    return tempArr;
}

// [1, "1", "", true, "true", false, "false", undefined, "undefined", null, "null", {…}, {…}, Array(0), Array(0), NaN, NaN]

for + 排序后去重 (改变了输出顺序)

不能对对象和NaN去重

注意,JS中的sort函数排序是按照字符编码的顺序进行排序,排序后,只需要循环判断数组中的每一个元素在这个数组中的indexOf的位置是否是自己的下标(有重复值时返回的第一个下标一定不是当前元素的下标)

1、使用indexOf比较下标

function unique(arr) {
    arr = arr.sort();
    var tempArr = [];
    var len = arr.length;
    for(var i = 0; i<len; i++) {
        if(arr.indexOf(arr[i]) === i) {
            tempArr.push(arr[i]);
        }
    }
    return tempArr;
}

// 这个方法的输出顺序会改变,且NaN会被忽略
// ["", Array(0), Array(0), 1, "1", {…}, {…}, false, "false", null, "null", true, "true", "undefined", undefined]

2、相邻数据是否相等判断法 另外一种是判断相邻两个元素是否相等,因为排序后的数组,如果有重复的值那就肯定会有相邻元素相等的情况。

function unique(arr) {
    arr = arr.sort();
    var tempArr = [];
    var len = arr.length;
    for(var i = 1; i<len; i++) {
        if(arr[i] !== arr[i-1]) {
            tempArr.push(arr[i-1]);
        }
    }
    tempArr.push(arr[len - 1]);
    return tempArr;
}

  • for循环同样可以通过for of 和foreach来改写,都是循环遍历

  • 排序去重的方式中,通过对比前一个后一个值的方法会出现问题,例如当'undefined', undefined, 'undefined'几个被sort排序后,不会区分字符串类型,这时候前后对比会判断不出来,同理,null和'null', null这样的顺序出现时,会出现没有正确去重的情况。不过可以用于普通数组的去重处理。如数组等于

    [1,'1',1, '1', '', '', true,'true',true,false,'false',false, null,'null',null]

    时,此时输出其实为:

    ["", 1, "1", 1, "1", false, "false", false, null, "null", null, true, "true", true]

filter+排序后去重 (改变了输出顺序)

ES5 提供的 filter 方法可以代替for循环,下面改写前面的两种去重方式:

filter + indexOf:

function unique (arr) {
    return arr.sort().filter(function(item, index) {
        return arr.indexOf(item) === index;
    });
}

// ["", Array(0), Array(0), 1, "1", {…}, {…}, false, "false", null, "null", true, "true", "undefined", undefined]

filter + 相邻数据是否相等:

function unique (arr) {
    return arr.sort().filter(function(item, index) {
        return item !== arr[index-1];
    });
    
}

// ["", Array(0), Array(0), 1, "1", NaN, NaN, {…}, {…}, false, "false", null, "null", null, true, "true", true, "undefined", undefined]

该方法同上一项 for+相邻数据是否相等 的方法会产生相同问题

利用对象存储

PS: 下面这个方法一有问题,当数组中存在字符串区分不了,比如1和'1',这个方法会判定为重复,所以只适用于纯number类型或同一类型的数组

方法一:

// 1、for循环
function unique(arr) {
    var obj = {};
    var len = arr.length;
    var tempArr = [];
    for(var i = 0; i < len; i++) {
        if(!obj.hasOwnProperty(arr[i])) {
            tempArr.push(arr[i]);
            obj[arr[i]] = true;
        }
    }
    return tempArr;
}

// 2、filter
function unique (arr) {
    var obj = {};
    return arr.filter(function(item) {
        return obj.hasOwnProperty(item) ? false : (obj[item] = true);
    })
}

// 输出:[1, "", true, false, undefined, null, {…}, NaN]
// 该方法将1和'1'当作重复元素进行处理了

解决办法:

使用typeof item + item拼接字符串作为对象的key值,例如1存储为number1,'1'存储为string1:

方法二:

// 1、for循环
function unique(arr) {
    var obj = {};
    var len = arr.length;
    var tempArr = [];
    for(var i = 0; i < len; i++) {
        if(!obj.hasOwnProperty(typeof arr[i]+arr[i])) {
            tempArr.push(arr[i]);
            obj[typeof arr[i]+arr[i]] = true;
        }
    }
    return tempArr;
}


// 2、filter
function unique (arr) {
    var obj = {};
    return arr.filter(function(item) {
        return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true);
    })
}

// [1, "1", "", true, "true", false, "false", undefined, "undefined", null, "null", {…}, Array(0), NaN]

注意: 优化后的这种方式既能对对象去重也能对NaN去重

循环 + includes

function unique(array) {
    var temp = [];
    array.forEach(element => {
        if(!temp.includes(element)) {temp.push(element)};
    });
    return temp;
}

// [1, "1", "", true, "true", false, "false", undefined, "undefined", null, "null", {…}, {…}, Array(0), Array(0), NaN]

注意: filter的这种方式不能对对象去重,但能对NaN去重

ES6的解决办法:Set、Map

ES6中提供了新的数据结构 Set。类似于数组,但是成员的值都是唯一的,没有重复的值。

function unique(array) {
   return Array.from(new Set(array));
}

接着使用解构赋值改写一下:

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

Map结构:

Es6提供了Map数据结构,它类似于对象,也是键值对的集合,但是‘键’的范围不限于字符串,各种类型的值(包括对象)都可以作为键名。

function unique(array) {
    const arr = new Map();
    return array.filter(function(item) {
        return !arr.has(item) && arr.set(item, 1);
    })
}

// [1, "1", "", true, "true", false, "false", undefined, "undefined", null, "null", {…}, {…}, Array(0), Array(0), NaN]

注意: Set和Map的方式对对象不去重,NaN去重

最后总结一下,一共六种去重方式:

  • 1、for+indexOf
  • 2、for+排序后(indexOf比较下标法或相邻数据相等法)去重
  • 3、前两种for改成filter
  • 4、对象存储法
  • 5、for(或者filter或者foreach) + includes , 本质同方法一
  • 6、ES6的Set结构

不过以上排序方法多少会存在一些问题, 如:

  • 只有方法4——对象存储法能对对象和NaN去重;

  • 方法6——Map和Set能对NaN去重但不能对对象去重;

  • 使用indexOf+排序后的方法会忽略NaN;

  • 排序方法中,相邻数据相等法对undefined、null、true等遇到字符串类型描述时去重会有问题;

参考文章

JS数组去重!!!一篇不怎么靠谱的"深度"水文

JavaScript专题之数组去重