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; }
相關文章
相關標籤/搜索