How to make your code faster using JavaScript Setsjavascript
我堅信不少開發者依舊與這些基本的全局對象打交道:numbers,strings,objects,arrays 和 booleans。java
大部分業務場景,以上這些已經夠用了。可是,若是你想讓你的代碼運行的儘量快、可擴展性儘量的好,那麼這些基本類型並不夠優秀。面試
在這篇文章,咱們將要討論如何利用 JS 的 Set
對象讓你的代碼運行的更快——尤爲是在它所處理的數據量大的時候。Array
和 Set
在處理數據時,兩則有太多的類似。可是使用 Set
所帶來的運行時優點,是 Array
沒法完成的。數組
Set
有何不一樣?根本的區別就是 Array
是 索引集合(index collection)
。這意味着,數據的值是以 索引(index)
排序的。測試
const arr = [A, B, C, D];
console.log(arr.indexOf(A)); // Result: 0
console.log(arr.indexOf(C)); // Result: 2
複製代碼
而 Set
則是 鍵集合(keyed collection)
。相比使用 索引
,Set
使用 鍵
來組織它的數據。一個 Set
中全部項都是按插入順序可迭代的,它不會有重複值。換句話說,Set
中的每一項都是獨一無二的。ui
Set
相比 Array
有些優點,特別是考慮到須要更快的運行時間:spa
indexOf()
或 includes()
去檢查一個項是否在數組中很慢。Set
中,你可使用 值
去刪除一項。而在 Array
中,相同的功能須要使用項的 索引
使用 splice()
方法。使用 索引
是很慢的Set
中新增一項比 Array
使用 push()
或者 unshift()
等方法新增一項要快的多。NaN
值: 你沒法使用 Array
的 indexOf()
或者 includes()
去定位 NaN
值,可是 Set
能夠而且可以存儲這個值Set
對象只存儲獨一無二的值,若是你想避免儲存重複值,這是比 Array
更好的選擇,由於使用 Array
,你須要使用額外的代碼去處理這種狀況。Note: 更多
Set
內置方法,請查閱 MDN Web Docs操作系統
使用 Array
去查找是一個爲 O(N)
的線性時間複雜度。換句話說,隨着數據量的提升,運行時間隨着增長。prototype
相比而言,使用 Set
去查找,不論是刪除仍是插入的時間複雜度都僅僅是 O(1)
——這意味着,運行時間不隨着數量的提升而增長。翻譯
Note: 想要了解更多關於時間複雜度的內容,請查閱個人文章 Understanding Big O Notation
雖然運行時間受使用的操做系統、數據的大小和其它的一些變量的影響,我但願個人測試結果能讓你對 Set
的速度有個直觀的感覺。
在開始運行以前,咱們簡單的將 Array
和 Set
填充 1000000
個值(0~999999)
let arr = [], set = new Set(), n = 1000000;
for (let i = 0; i < n; i++) {
arr.push(i);
set.add(i);
}
複製代碼
查找值 123123
:
let result;
console.time('Array');
result = arr.indexOf(123123) !== -1;
console.timeEnd('Array');
console.time('Set');
result = set.has(123123);
console.timeEnd('Set');
複製代碼
Array
: 0.173msSet
: 0.023msSet
快了 7.54 倍新增一個值,變量爲 n
:
console.time('Array');
arr.push(n);
console.timeEnd('Array');
console.time('Set');
set.add(n);
console.timeEnd('Set');
複製代碼
Array
: 0.018msSet
: 0.003msSet
快了 6.73 倍最後,咱們刪除一項(就刪除咱們剛新增的)。由於 Array
沒有原生刪除方法,咱們寫一個 helper
來完成這個功能:
const deleteFromArr = (arr, item) => {
let index = arr.indexOf(item);
return index !== -1 && arr.splice(index, 1);
};
複製代碼
進行咱們的測試:
console.time('Array');
deleteFromArr(arr, n);
console.timeEnd('Array');
console.time('Set');
set.delete(n);
console.timeEnd('Set');
複製代碼
Array
: 1.122msSet
: 0.015msSet
快了 74.13 倍!整體來講,咱們能夠看到在運行時間上,Set
相比 Array
優點巨大。如今咱們來看看 Set
的一些實踐:
若是你想要在 Array
中快速去重,你能夠將它轉爲 Set
。這是目前爲止最簡潔的方法。
const duplicateCollection = ['A', 'B', 'B', 'C', 'D', 'B', 'C'];
// 若是你想把 Array 轉成 Set
let uniqueCollection = new Set(duplicateCollection);
console.log(uniqueCollection) // Set(4) {"A", "B", "C", "D"}
// 若是你想讓你的值還是 `Array`
let uniqueCollection = [...new Set(duplicateCollection)];
console.log(uniqueCollection) // ["A", "B", "C", "D"]
複製代碼
在個人另外一篇文章中,我爲谷歌面試官的一個問題討論了一些解決方案。面試是使用 C++
,可是若是是 JS
,Set
會是最終解決方案的關鍵點。
若是你想要更深刻了解這些解決方案,我推薦閱讀原文,可是這裏,我簡單的介紹一下解決方案。
給一個未排序的整數數組和一個值 sum
,若是數組中任意兩項相加等於 sum
,則返回 true
,不然返回 false
。
如給定數組 [3, 5, 1, 4]
和值 9
,咱們的方法應該返回 true
,由於 4 + 5 = 9
。
這裏解釋思路,不翻譯了,看代碼就能懂。
const findSum = (arr, val) => {
let searchValues = new Set();
searchValues.add(val - arr[0]);
for (let i = 1, length = arr.length; i < length; i++) {
let searchVal = val - arr[i];
if (searchValues.has(arr[i])) {
return true;
} else {
searchValues.add(searchVal);
}
};
return false;
};
複製代碼
更簡潔的版本:
const findSum = (arr, sum) => arr.some((set => n => set.has(n) || !set.add(sum - n))(new Set));
複製代碼
由於 Set.prototype.has()
時間複雜度只有 O(1)
, 使用 Set
存儲數據,結合 Array
的循環,咱們最終的時間複雜度爲 O(N)
。
若是咱們依賴 Array.prototype.indexOf()
或 Array.prototype.includes()
,而二者的時間複雜度都是 O(N)
, 咱們最終的時間複雜度會達到 O(N²)
。太慢了!
但願本文對你有所幫助!