也谈口试必备题目之 JavaScript 数组去重

Why underscore

(以为这部份眼熟的能够直接跳到下一段了…)

近来最先看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 设想中。

浏览一些有名框架类库的源码,就好像和一个个巨匠对话,你会学到许多。为何是 underscore?最主要的原因是 underscore 简短精干(约 1.5k 行),封装了 100 多个有效的要领,耦合度低,异常合适逐一要领浏览,合适楼主如许的 JavaScript 初学者。从中,你不仅能够学到用 void 0 替代 undefined 防止 undefined 被重写等一些小技能 ,也能够学到变量范例推断、函数撙节&函数去抖等经常使用的要领,还能够学到许多浏览器兼容的 hack,更能够学到作者的团体设想思绪以及 API 设想的道理(向后兼容)。

以后楼主会写一系列的文章跟人人分享在源码浏览中进修到的学问。

迎接围观~ (假如有兴致,迎接 star & watch~)您的关注是楼主继承写作的动力

数组去重

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

数组去重,平常需求是给你一个数组,挪用去重要领,返回数值副本,副本中没有反复元素。平常来讲,两个元素经由过程 === 比较返回 true 的视为雷同元素,须要去重,所以,1"1" 是差别的元素,1new 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。

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"]

要领二

法一是将原数组中的元素和效果数组中的元素逐一比较,我们能够换个思绪,将原数组中反复元素的末了一个元素放入效果数组中。

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 出如今了数组末了面,由于效果数组取的是元素末了一次涌现的位置。

要领三(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

Read More

    原文作者:fish
    原文地址: https://segmentfault.com/a/1190000005650732
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞