給急躁的程式設計師的 JavaScript(ES2022 版)
請支持這本書:購買捐款
(廣告,請不要阻擋。)

35 集合(Set



在 ES6 之前,JavaScript 沒有集合的資料結構。取而代之的是,使用了兩個解決方法

自 ES6 起,JavaScript 有一個資料結構 Set,它可以包含任意值,並快速執行成員檢查。

35.1 使用 Set

35.1.1 建立 Set

建立 Set 有三種常見的方式。

首先,你可以使用沒有任何參數的建構函數來建立一個空的 Set

const emptySet = new Set();
assert.equal(emptySet.size, 0);

其次,你可以將一個可迭代物件(例如陣列)傳遞給建構函數。可迭代物件的值會變成新 Set 的元素

const set = new Set(['red', 'green', 'blue']);

第三,.add() 方法會將元素加入 Set 中,而且可以串接使用

const set = new Set()
.add('red')
.add('green')
.add('blue');

35.1.2 加入、移除、檢查成員

.add() 會將一個元素加入 Set 中。

const set = new Set();
set.add('red');

.has() 會檢查一個元素是否為 Set 的成員。

assert.equal(set.has('red'), true);

.delete() 會從 Set 中移除一個元素。

assert.equal(set.delete('red'), true); // there was a deletion
assert.equal(set.has('red'), false);

35.1.3 判斷 Set 的大小並清除它

.size 包含 Set 中元素的數量。

const set = new Set()
  .add('foo')
  .add('bar');
assert.equal(set.size, 2)

.clear() 會移除 Set 中的所有元素。

set.clear();
assert.equal(set.size, 0)

35.1.4 迭代 Set

Set 是可迭代的,而 for-of 迴圈會如你預期般運作

const set = new Set(['red', 'green', 'blue']);
for (const x of set) {
  console.log(x);
}
// Output:
// 'red'
// 'green'
// 'blue'

正如你所見,Set 會保留插入順序。也就是說,元素總是會按照它們被加入的順序進行迭代。

由於 Set 是可迭代的,你可以使用 Array.from() 將它們轉換成陣列

const set = new Set(['red', 'green', 'blue']);
const arr = Array.from(set); // ['red', 'green', 'blue']

35.2 使用 Set 的範例

35.2.1 從陣列中移除重複值

將陣列轉換成 Set 再轉換回來,會從陣列中移除重複值

assert.deepEqual(
  Array.from(new Set([1, 2, 1, 2, 3, 3, 3])),
  [1, 2, 3]);

35.2.2 建立 Unicode 字元(編碼點)的集合

字串是可迭代的,因此可以用作 new Set() 的參數

assert.deepEqual(
  new Set('abc'),
  new Set(['a', 'b', 'c']));

35.3 哪些 Set 元素被視為相等?

與 Map 鍵一樣,Set 元素的比較方式類似於 ===,但例外情況是 NaN 等於它自己。

> const set = new Set([NaN, NaN, NaN]);
> set.size
1
> set.has(NaN)
true

=== 一樣,兩個不同的物件永遠不會被視為相等(而且目前沒有辦法改變這一點)

> const set = new Set();

> set.add({});
> set.size
1

> set.add({});
> set.size
2

35.4 缺少的 Set 操作

Set 缺少一些常見的操作。這樣的操作通常可以透過以下方式實作

35.4.1 聯集(ab

計算兩個 Set ab 的聯集,表示建立一個包含 ab 中所有元素的 Set。

const a = new Set([1,2,3]);
const b = new Set([4,3,2]);
// Use spreading to concatenate two iterables
const union = new Set([...a, ...b]);

assert.deepEqual(Array.from(union), [1, 2, 3, 4]);

35.4.2 交集(ab

計算兩個 Set ab 的交集,表示建立一個包含 a 中也存在於 b 中的那些元素的 Set。

const a = new Set([1,2,3]);
const b = new Set([4,3,2]);
const intersection = new Set(
  Array.from(a).filter(x => b.has(x))
);

assert.deepEqual(
  Array.from(intersection), [2, 3]
);

35.4.3 差集(a \ b

計算兩個 Set ab 的差集,表示建立一個包含 a 中不存在於 b 中的那些元素的 Set。這個操作有時也稱為減號(−)。

const a = new Set([1,2,3]);
const b = new Set([4,3,2]);
const difference = new Set(
  Array.from(a).filter(x => !b.has(x))
);

assert.deepEqual(
  Array.from(difference), [1]
);

35.4.4 對 Set 進行對應

Set 沒有 .map() 方法。但我們可以借用陣列的

const set = new Set([1, 2, 3]);
const mappedSet = new Set(
  Array.from(set).map(x => x * 2)
);

// Convert mappedSet to an Array to check what’s inside it
assert.deepEqual(
  Array.from(mappedSet), [2, 4, 6]
);

35.4.5 過濾 Set

我們無法直接對 Set 執行 .filter(),因此需要使用對應的陣列方法

const set = new Set([1, 2, 3, 4, 5]);
const filteredSet = new Set(
  Array.from(set).filter(x => (x % 2) === 0)
);

assert.deepEqual(
  Array.from(filteredSet), [2, 4]
);

35.5 快速參考:Set<T>

35.5.1 建構函式

35.5.2 Set<T>.prototype:單一 Set 元素

35.5.3 Set<T>.prototype:所有 Set 元素

35.5.4 Set<T>.prototype:迭代和迴圈

35.5.5 與 Map 的對稱性

以下兩個方法主要存在,以便 Set 和 Map 具有類似的介面。每個 Set 元素都會處理為一個 Map 項目,其鍵和值都是該元素。

.entries() 讓您可以將 Set 轉換為 Map

const set = new Set(['a', 'b', 'c']);
const map = new Map(set.entries());
assert.deepEqual(
  Array.from(map.entries()),
  [['a','a'], ['b','b'], ['c','c']]
);

35.6 常見問題:Set

35.6.1 為什麼 Set 有 .size,而陣列有 .length

此問題的解答請見 §33.6.4「為什麼 Map 有 .size,而陣列有 .length?」

  測驗

請參閱 測驗應用程式