20多種數組去重?看懂這4種,全都不在話下

爲何寫這篇文章

在掘金上每過一段時間就會出現一次題材類似的討論,例如面試,手寫函數,而此題材也不例外——數組去重。我看到過論壇上出現過的一次次的數組去重的文章,可是我的以爲可能都不太完善,因此,我指望嘗試對 JS 數組去重進行一次 「系統性」 的總結。html

資料

前提

測試用例

該用例包含了全部簡單基本類型以及對象,基本能夠涵蓋全部常見數據的可能es6

let a = {};
let b = { a: 1 };
let testArr = [1, 1, "1", "1", "a", "a", {}, {}, { a: 1 }, a, a, b, [], [], [1], undefined, undefined, null, null, NaN, NaN];
複製代碼

去重標準

如下的代碼以 lodash 中默認去重函數的去重結果爲標準,lodash uniq 函數去重結果以下面試

// lodash@4.17.15
_.uniq(testArr);
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null, NaN];
複製代碼

去重 4 大類型

對於 JS 數組去重來講,其實萬變不離其中,我簡單的總結了如下 4 種去重類型,而圍繞着各類類型,因爲 JS 豐富的函數以及工具能力,能夠有多種實現方法,無論是採用 forEach,reduce,filter 等來循環並拼裝新數組,仍是用最基礎的 for 循環以及 push 來循環並組裝新數組,暫時均可以概括到如下的 4 個類型數組

數組元素比較型

該方法是將數組的值取出與其餘值比較並修改數組函數

雙層 for 循環

取出一個元素,將其與其後全部元素比較,若在其後發現相等元素,則將後者刪掉工具

function uniq(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;
}

// 運行結果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null, NaN, NaN]
// 與 lodash 結果相比 NaN 沒有去掉
複製代碼

因爲 NaN === NaN 等於 false,因此重複的 NaN 並無被去掉性能

排序並進行相鄰比較

先對數組內元素進行排序,再對數組內相鄰的元素兩兩之間進行比較,經測試,該方法受限於 sort 的排序能力,因此若數組不存在比較複雜的對象(複雜對象難以排序),則可嘗試此方法學習

function uniq(arr) {
    arr.sort();
    for (let i = 0; i < arr.length - 1; i++) {
        arr[i] === arr[i + 1] && arr.splice(i + 1, 1) && i--;
    }
    return arr;
}
// 運行結果
//[[], [], 1, "1", [1], NaN, NaN, {}, {}, { a: 1 }, {}, { a: 1 }, "a", null, undefined];
// 與 lodash 結果相比 NaN 沒有去掉,且對象的去重出現問題
複製代碼

一樣因爲 NaN === NaN 等於 false,因此重複的 NaN 並無被去掉,而且因爲 sort 沒有將對象很好的排序,在對象部分,會出現一些去重失效測試

查找數組元素位置型

該類型即針對每一個數組元素進行一次查找其在數組內的第一次出現的位置,若第一次出現的位置等於該元素此時的索引,即收集此元素ui

indexOf

indexOf 來查找元素在數組內第一次出現的位置,若位置等於當前元素的位置,則收集

function uniq(arr) {
    let res = [];
    for (let i = 0; i < arr.length; i++) {
        if (arr.indexOf(arr[i]) === i) {
            res.push(arr[i]);
        }
    }
    return res;
}
// 運行結果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null];
// 與 lodash 結果相比 少了 NaN
複製代碼

indexOf 採用與 === 相同的值相等判斷規則,因此在數組內沒有元素與 NaN 相等,包括它本身,因此 NaN 一個都不會被留下

findIndex

findIndex 方法來查找元素在數組內第一次出現的位置

function uniq(arr) {
    let res = [];
    for (let i = 0; i < arr.length; i++) {
        if (arr.findIndex(item => item === arr[i]) === i) {
            res.push(arr[i]);
        }
    }
    return res;
}
// 運行結果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null];
// 與 lodash 結果相比 少了 NaN
複製代碼

結果原理和 indexOf 相同,由於用了 === 的規則來判斷元素是否相等,但此方法至關於雙層 for 循環

查找元素是否存在型

該方法基本依託 includes 方法來判斷對應元素是否在新數組內存在,若不存在則收集

function uniq(arr) {
    let res = [];
    for (let i = 0; i < arr.length; i++) {
        if (!res.includes(arr[i])) {
            res.push(arr[i]);
        }
    }
    return res;
}
// 運行結果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null, NaN];
// 與 lodash 結果相同
複製代碼

includes 方法採用 SameValueZero 判斷規則,因此能夠判斷出並去重 NaN

依託數據類型特性型

該方案依託於數據類型的不重複特性,以達到去重效果

Map

Map 類型的數據能夠像 Object 同樣,在設定元素鍵值對的時候能夠保證鍵的惟一,而且將鍵的類型拓展到了基本全部元素,包括對象,在設定好一個惟一鍵的 Map 數據類型後,再用其自帶的 Map.prototype.keys() 方法取到相應的鍵類數組,最後將類數組進行一次轉化便可

function uniq(arr) {
    let map = new Map();
    for (let i = 0; i < arr.length; i++) {
        !map.has(arr[i]) && map.set(arr[i], true);
    }
    return [...map.keys()];
}
// 運行結果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null, NaN];
// 與 lodash 結果相同
複製代碼

Set

與 Map 相似,運用數據類型的特性完成去重,這個方法也是最熱門的方法

function uniq(arr) {
    return [...new Set(arr)];
}
// 運行結果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null, NaN];
// 與 lodash 結果相同
複製代碼

性能測試

測試用例

這裏簡單(不嚴謹)的將剛纔使用的測試例子拼接 100 萬次

let a = {};
let b = { a: 1 };
let testArr = [1, 1, "1", "1", "a", "a", {}, {}, { a: 1 }, a, a, b, [], [], [1], undefined, undefined, null, null, NaN, NaN];
let arr = [];
for (let i = 0; i < 1000000; i++) {
    arr.push(...testArr);
}
uniq(arr);
複製代碼

先說結果,在簡單的測試用例大概 2000 萬條數據下,indexOf 的方案速度相對最快

以上,就是我針對數組去重的總結,但願能夠給你們一點 JS 數組去重的靈感,有不足的地方,歡迎指出一塊兒討論 😋

我以前還寫過兩個小工具,但願能夠幫助到你們,一塊兒學習一塊兒進步。

相關文章
相關標籤/搜索