js数组去重

数组去重git

今天要聊的,也是我之前笔试时碰到过的一个问题,数组去重,不知道如今的笔试题还考不考这个?github

数组去重,通常需求是给你一个数组,调用去重方法,返回数值副本,副本中没有重复元素。通常来讲,两个元素经过 === 比较返回 true 的视为相同元素,须要去重,因此,1 和 "1" 是不一样的元素,1 和 new Number(1) 是不一样的元素,{} 和 {} 是不一样的元素(引用不一样)。(固然若是需求认为 {} 和 {} 算做相同的元素,那么解法就不同了)面试

方法一数组

无需思考,咱们能够获得 O(n^2) 复杂度的解法。定义一个变量数组 res 保存结果,遍历须要去重的数组,若是该元素已经存在在 res 中了,则说明是重复的元素,若是没有,则放入 res 中。浏览器

function unique(a) { var res = []; for (var i = 0, len = a.length; i < len; i++) { ​ var item = a[i]; ​ for (var j = 0, jLen = res.length; j < jLen; j++) { ​ if (res[j] === item) ​ break; ​ } ​ if (j === jLen) ​ res.push(item); } return res; } var a = [1, 1, '1', '2', 1]; var ans = unique(a); console.log(ans); // => [1, "1", "2"]

代码很是简单,那么是否能更简洁些?若是不考虑浏览器兼容,咱们能够用 ES5 提供的 Array.prototype.indexOf 方法来简化代码。函数

function unique(a) { var res = []; for (var i = 0, len = a.length; i < len; i++) { ​ var item = a[i]; ​ (res.indexOf(item) === -1) && res.push(item); } return res; } var a = [1, 1, '1', '2', 1]; var ans = unique(a); console.log(ans); // => [1, "1", "2"]

既然用了 indexOf,那么不妨再加上 filter。this

function unique(a) { var res = a.filter(function(item, index, array) { ​ return array.indexOf(item) === index; }); return res; } var a = [1, 1, '1', '2', 1]; var ans = unique(a); console.log(ans); // => [1, "1", "2"]

方法二spa

法一是将原数组中的元素和结果数组中的元素一一比较,咱们能够换个思路,将原数组中重复元素的最后一个元素放入结果数组中。prototype

function unique(a) { var res = []; for (var i = 0, len = a.length; i < len; i++) { ​ for (var j = i + 1; j < len; j++) { ​ // 这一步十分巧妙
// 若是发现相同元素
// 则 i 自增进入下一个循环比较
if (a[i] === a[j]) ​ j = ++i; ​ } ​ res.push(a[i]); } return res; } var a = [1, 1, '1', '2', 1]; var ans = unique(a); console.log(ans); // => ["1", "2", 1]

虽然复杂度仍是 O(n^2),可是能够看到结果不一样,1 出如今了数组最后面,由于结果数组取的是元素最后一次出现的位置。code

方法三(sort)

若是笔试面试时只答出了上面这样 O(n^2) 的方案,可能还不能使面试官满意,下面就来讲几种进阶方案。

将数组用 sort 排序后,理论上相同的元素会被放在相邻的位置,那么比较先后位置的元素就能够了。

function unique(a) { return a.concat().sort().filter(function(item, pos, ary) { ​ return !pos || item != ary[pos - 1]; }); } var a = [1, 1, 3, 2, 1, 2, 4]; var ans = unique(a); console.log(ans); // => [1, 2, 3, 4]

可是问题又来了,1 和 "1" 会被排在一块儿,不一样的 Object 会被排在一块儿,由于它们 toString() 的结果相同,因此会出现这样的错误:

var a = [1, 1, 3, 2, 1, 2, 4, '1']; var ans = unique(a); console.log(ans); // => [1, 2, 3, 4]

固然你彻底能够针对数组中可能出现的不一样类型,来写这个比较函数。不过这彷佛有点麻烦。

方法四 (object)

用 JavaScript 中的 Object 对象来当作哈希表,这也是几年前笔试时的解法,跟 sort 同样,能够去重彻底由 Number 基本类型组成的数组。

function unique(a) { var seen = {}; return a.filter(function(item) { ​ return seen.hasOwnProperty(item) ? false : (seen[item] = true); }); } var a = [1, 1, 3, 2, 1, 2, 4]; var ans = unique(a); console.log(ans); // => [1, 3, 2, 4]

仍是和方法三同样的问题,由于 Object 的 key 值都是 String 类型,因此对于 1 和 "1" 没法分别,咱们能够稍微改进下,将类型也存入 key 中。

function unique(a) { var ret = []; var hash = {}; for (var i = 0, len = a.length; i < len; i++) { ​ var item = a[i]; ​ var key = typeof(item) + item; ​ if (hash[key] !== 1) { ​ ret.push(item); ​ hash[key] = 1; ​ } } return ret; } var a = [1, 1, 3, 2, '4', 1, 2, 4, '1']; var ans = unique(a); console.log(ans); // => [1, 3, 2, "4", 4, "1"]

虽然解决了讨厌的 1 和 "1" 的问题,可是还有别的问题!

var a = [{name: "hanzichi"}, {age: 30}, new String(1), new Number(1)]; var ans = unique(a); console.log(ans); // => [Object, String]

可是若是数组元素所有是基础类型的 Number 值,键值对法应该是最高效的!

方法五 (ES6)

ES6 部署了 Set 以及 Array.from 方法,太强大了!若是浏览器支持,彻底能够这样:

function unique(a) { return Array.from(new Set(a)); } var a = [{name: "hanzichi"}, {age: 30}, new String(1), new Number(1)]; var ans = unique(a); console.log(ans); // => [Object, Object, String, Number]

_.unique

最后来看看 underscore 对此的实现方式,underscore 将此封装到了 .unique 方法中,调用方式为 .unique(array, [isSorted], [iteratee])。其中第一个参数是必须的,是须要去重的数组,第二个参数可选,若是数组有序,则能够传入布尔值 true,第三个参数可选,若是须要对数组迭代的结果去重,则能够传入一个迭代函数。而数组元素去重是基于 === 运算符的。

其实很简单,underscore 中的实现方式和上面的方法一类似。

咱们来看它的核心代码:

for (var i = 0, length = getLength(array); i length; i++) { var value = array[i], ​ // 若是指定了迭代函数
// 则对数组每个元素进行迭代
 ​ computed = iteratee ? iteratee(value, i, array) : value; // 若是是有序数组,则当前元素只需跟上一个元素对比便可

// 用 seen 变量保存上一个元素

if (isSorted) { ​ // 若是 i === 0,则直接 push
// 不然比较当前元素是否和前一个元素相等
if (!i || seen !== computed) result.push(value); ​ // seen 保存当前元素,供下一次对比
 ​ seen = computed; } else if (iteratee) { ​ // 若是 seen[] 中没有 computed 这个元素值
if (!_.contains(seen, computed)) { ​ seen.push(computed); ​ result.push(value); ​ } } else if (!_.contains(result, value)) { ​ // 若是不用通过迭代函数计算,也就不用 seen[] 变量了
 ​ result.push(value); } }

外面的循环遍历数组元素,对于每一个元素,若是数组有序,则和前一个元素比较,若是相同,则已经出现过,不加入到结果数组中,不然则加入。而若是有迭代函数,则计算传入迭代函数后的值,对值去重,调用 .contains 方法,而该方法的核心就是调用 .indexOf 方法,和咱们上面说的方法一殊途同归。

关于 _.unique 方法的详细代码,能够参考 https://github.com/hanzichi/underscore-analysis/blob/master/underscore-1.8.3.js/src/underscore-1.8.3.js#L519-L547

//1.
Array.prototype.unique1 = function () { var n = []; //一个新的临时数组
  for (var i = 0; i < this.length; i++) //遍历当前数组
 { //若是当前数组的第i已经保存进了临时数组,那么跳过,
    //不然把当前项push到临时数组里面
    if (n.indexOf(this[i]) == -1) n.push(this[i]); } return n; } //2.
Array.prototype.unique2 = function() { var n = {},r=[]; //n为hash表,r为临时数组
    for(var i = 0; i < this.length; i++) //遍历当前数组
 { if (!n[this[i]]) //若是hash表中没有当前项
 { n[this[i]] = true; //存入hash表
            r.push(this[i]); //把当前数组的当前项push到临时数组里面
 } } return r; } //3.
Array.prototype.unique3 = function() { var n = [this[0]]; //结果数组
    for(var i = 1; i < this.length; i++) //从第二项开始遍历
 { //若是当前数组的第i项在当前数组中第一次出现的位置不是i,
        //那么表示第i项是重复的,忽略掉。不然存入结果数组
        if (this.indexOf(this[i]) == i) n.push(this[i]); } return n; }
相关文章
相关标签/搜索