【譯】使用Set使你的代碼運行更快

How to make your code faster using JavaScript Setsjavascript

我堅信不少開發者依舊與這些基本的全局對象打交道:numbers,strings,objects,arrays 和 booleans。java

大部分業務場景,以上這些已經夠用了。可是,若是你想讓你的代碼運行的儘量快、可擴展性儘量的好,那麼這些基本類型並不夠優秀。面試

在這篇文章,咱們將要討論如何利用 JS 的 Set 對象讓你的代碼運行的更快——尤爲是在它所處理的數據量大的時候。ArraySet 在處理數據時,兩則有太多的類似。可是使用 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: 你沒法使用 ArrayindexOf() 或者 includes() 去定位 NaN 值,可是 Set 能夠而且可以存儲這個值
  • 去重: Set 對象只存儲獨一無二的值,若是你想避免儲存重複值,這是比 Array 更好的選擇,由於使用 Array,你須要使用額外的代碼去處理這種狀況。

Note: 更多 Set 內置方法,請查閱 MDN Web Docs操作系統

什麼是時間複雜度?

使用 Array 去查找是一個爲 O(N) 的線性時間複雜度。換句話說,隨着數據量的提升,運行時間隨着增長。prototype

相比而言,使用 Set 去查找,不論是刪除仍是插入的時間複雜度都僅僅是 O(1)——這意味着,運行時間不隨着數量的提升而增長。翻譯

Note: 想要了解更多關於時間複雜度的內容,請查閱個人文章 Understanding Big O Notation

那麼 Set 究竟有多快呢?

雖然運行時間受使用的操做系統、數據的大小和其它的一些變量的影響,我但願個人測試結果能讓你對 Set 的速度有個直觀的感覺。

準備測試

在開始運行以前,咱們簡單的將 ArraySet 填充 1000000 個值(0~999999)

let arr = [], set = new Set(), n = 1000000;
for (let i = 0; i < n; i++) {
  arr.push(i);
  set.add(i);
}
複製代碼

測試1:查找

查找值 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.173ms
  • Set: 0.023ms
  • Set 快了 7.54 倍

測試2: 新增

新增一個值,變量爲 n

console.time('Array'); 
arr.push(n);
console.timeEnd('Array');
console.time('Set'); 
set.add(n);
console.timeEnd('Set');
複製代碼
  • Array: 0.018ms
  • Set: 0.003ms
  • Set 快了 6.73 倍

測試3:刪除

最後,咱們刪除一項(就刪除咱們剛新增的)。由於 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.122ms
  • Set: 0.015ms
  • 這一次,Set 快了 74.13 倍!

整體來講,咱們能夠看到在運行時間上,Set 相比 Array 優點巨大。如今咱們來看看 Set 的一些實踐:

用例1: 數組去重

若是你想要在 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"]
複製代碼

用例2:谷歌面試題

在個人另外一篇文章中,我爲谷歌面試官的一個問題討論了一些解決方案。面試是使用 C++,可是若是是 JSSet 會是最終解決方案的關鍵點。

若是你想要更深刻了解這些解決方案,我推薦閱讀原文,可是這裏,我簡單的介紹一下解決方案。

給一個未排序的整數數組和一個值 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²)。太慢了!

但願本文對你有所幫助!

相關文章
相關標籤/搜索