`Set` オブジェクト

`Set` オブジェクト

この記事ではSet オブジェクトについて説明します。

Set オブジェクトについて実際的なサンプルを含めて解説します。

YouTube Video

Set オブジェクト

Set は、重複のない一意な値のコレクションを扱うための組み込みオブジェクトです。配列よりも「重複排除」や「存在確認」が簡潔に記述でき、和や積などの集合演算を実装しやすくなります。

基本:Set の生成と基本操作

まずは Set の作り方と、要素の追加、削除、存在確認、サイズの確認を示します。

以下は、新しい Set を作成し、add, has, delete, size を確認する基本パターンです。

 1// Create a Set and demonstrate add, has, delete, and size
 2const s = new Set();
 3
 4s.add(1);
 5s.add(2);
 6s.add(2); // duplicate, ignored
 7
 8console.log(s.has(1)); // true
 9console.log(s.has(3)); // false
10
11s.delete(2);
12console.log(s.size); // 1
13
14console.log([...s]); // [1]
  • このコードのように Set は同一のプリミティブ値の重複を自動で排除し、size で要素数を得られます。

イテレーション(反復)方法

Set は反復可能なので for...offorEach で繰り返すことができます。順序は挿入順です。

for...offorEach の典型的な使い方を示します。

 1// Iterate a Set with for...of and forEach
 2const s = new Set(['a', 'b', 'c']);
 3
 4for (const v of s) {
 5  console.log('for...of:', v);
 6}
 7
 8s.forEach((value, sameValue, setRef) => {
 9  // Note: second arg is same as first for Set API to match Map signature
10  console.log('forEach:', value);
11});
  • forEach のコールバックは、マップとの互換性のため value, value, set という形ですが、実用上は最初の value を使えば十分です。

配列とSetの相互変換(重複削除に便利)

ここでは、配列の重複を削除する簡単なテクニックと、Set から配列に戻す方法を示します。

以下は、配列から Set を経由して重複を取り除く例です。

1// Deduplicate an array using Set
2const arr = [1, 2, 2, 3, 3, 3];
3const deduped = [...new Set(arr)];
4console.log(deduped); // [1, 2, 3]
5
6// Convert a Set to an array using Array.from
7const s = new Set([4, 5, 6]);
8const arrFromSet = Array.from(s);
9console.log(arrFromSet); // [4, 5, 6]
  • このパターンは短く速いため、配列の重複削除で頻繁に使われます。プリミティブ値に対して特に有効です。

オブジェクトと参照の扱い

Set 内でのオブジェクトは参照で比較されるため、同じ内容でも別インスタンスは別要素になります。

次のコードでは、オブジェクトを Set に入れたときの挙動を確認できます。

 1// Objects are compared by reference in a Set
 2const obj1 = { x: 1 };
 3const obj2 = { x: 1 };
 4
 5const s = new Set();
 6s.add(obj1);
 7s.add(obj2);
 8
 9console.log(s.size); // 2 (different references)
10console.log(s.has(obj1)); // true
11console.log(s.has({ x: 1 })); // false (different object)
  • オブジェクトの重複判定は同一参照かどうかで決まるため、オブジェクトの内容だけで重複を排除したい場合はシリアライズ等の工夫が必要です。

特殊な値:NaN-0 / +0 の扱い

Set は値の重複判定に Same-value-zero という比較ルールを使います。これは、数値に関して次のような特徴を持つ比較方法です。

  • NaNNaN と等しいとみなされる
  • 正の+0と負の-0は区別されず、同じ値として扱われる

そのため、Set にこれらの値を追加すると、次のような挙動になります。

 1// NaN and zero behavior in Set
 2const s = new Set();
 3
 4s.add(NaN);
 5s.add(NaN);
 6console.log(s.size); // 1 (NaN considered the same)
 7
 8s.add(+0);
 9s.add(-0);
10console.log(s.size); // still 2 (NaN + 0)
11console.log([...s]); // [NaN, 0] (order may vary but only one zero)
  • 通常の比較(NaN === NaN)では false になりますが、Set の中では NaN は「同じ値」としてまとめられます。
  • 正の+0と負の-0は数学的には区別できますが、Set ではどちらも単に 0 として扱われます。
  • その結果、Set には「NaNが1つ」「0が1つ」だけが残ります。
  • Set の比較ルールは Object.is と似ていますが、完全に同じではありませんObject.is(+0, -0)false ですが、Set では同一視される点に注意してください。

よく使うユーティリティ:集合演算(和・積・差)

Set を使うと集合演算が読みやすく書けます。以下は一般的な実装例です。

次は、union(和)、intersection(積)、difference(差)を関数化した例です。

 1// Set operations: union, intersection, difference
 2function union(a, b) {
 3  return new Set([...a, ...b]);
 4}
 5
 6function intersection(a, b) {
 7  return new Set([...a].filter(x => b.has(x)));
 8}
 9
10function difference(a, b) {
11  return new Set([...a].filter(x => !b.has(x)));
12}
13
14// Demo
15const A = new Set([1, 2, 3]);
16const B = new Set([3, 4, 5]);
17
18console.log('union', [...union(A, B)]); // [1,2,3,4,5]
19console.log('intersection', [...intersection(A, B)]); // [3]
20console.log('difference A\\B', [...difference(A, B)]); // [1,2]
  • Set と配列の組み合わせでフィルタを使うと集合演算がシンプルに書けます。データ量が大きいときは has の O(1) 性質が効いて高速です。

実践例:配列の差分(追加・削除アイテムを見つける)

以下は、2つの配列(旧リストと新リスト)の差分を Set を使って求める例です。差分で「追加された要素」と「削除された要素」を見つけます。

 1// Find added and removed items between two arrays
 2function diffArrays(oldArr, newArr) {
 3  const oldSet = new Set(oldArr);
 4  const newSet = new Set(newArr);
 5
 6  const added = [...newSet].filter(x => !oldSet.has(x));
 7  const removed = [...oldSet].filter(x => !newSet.has(x));
 8
 9  return { added, removed };
10}
11
12const oldList = [1, 2, 3];
13const newList = [2, 3, 4, 5];
14
15console.log(diffArrays(oldList, newList));
16// { added: [4,5], removed: [1] }
  • この方法は ID リストやタグリストなどの差分検出に非常に便利です。プリミティブ値で使うのが最も簡単です。

WeakSet と Set の違い(メモリ管理)

WeakSetSet と似ていますが、弱参照で、ガベージコレクションされうる点が異なります。以下は、WeakSet の基本的な使い方です。

1// WeakSet basics (objects only, not iterable)
2const ws = new WeakSet();
3let obj = { id: 1 };
4ws.add(obj);
5
6console.log(ws.has(obj)); // true
7
8obj = null; // Now the object is eligible for GC; WeakSet won't prevent collection

WeakSet はオブジェクトのみ保持でき、反復もできません。以下では、オブジェクトのみ保持でき、反復ができないなどの制約の例を示します。

 1// WeakSet basics (objects only, not iterable)
 2const ws = new WeakSet();
 3
 4// --- Only objects can be added ---
 5try {
 6	ws.add(1); // number
 7} catch (e) {
 8	console.log("Error: WeakSet can only store objects. Adding a number is not allowed.");
 9}
10
11try {
12	ws.add("text"); // string
13} catch (e) {
14	console.log("Error: WeakSet can only store objects. Adding a string is not allowed.");
15}
16
17// --- WeakSet is not iterable ---
18try {
19	for (const value of ws) {
20		console.log(value);
21	}
22} catch (e) {
23	console.log("Error: WeakSet is not iterable. You cannot use for...of to loop over its elements.");
24}
25
26// --- Cannot convert to array ---
27try {
28	console.log([...ws]);
29} catch (e) {
30	console.log("Error: WeakSet cannot be converted to an array because it does not support iteration.");
31}
32
33// The object becomes eligible for garbage collection
34let obj = { id: 1 };
35ws.add(obj);
36obj = null;
  • WeakSet は一時的にオブジェクトの存在を追跡したい場合に有用ですが、要素列挙やサイズ取得ができない制約があります。

パフォーマンスと使用場面の判断基準

Set を使うかどうかを判断する際は、性能特性とデータの性質を理解しておくことが重要です。

  • hasadddelete は平均して O(1) に近い性能で動作します。そのため、要素の存在確認や重複排除を頻繁に行う場面では、配列よりも有利になることが多いです。
  • オブジェクトの**中身(値)**で重複を判定したい場合は注意が必要です。Set は参照で比較するため、必要に応じて ID などのキーを使う、または シリアライズしてプリミティブ値に変換してから Set を利用するのが現実的な方法です。
  • Set小〜中規模のコレクションではコードの可読性向上に役立ちます。一方で、非常に大量の要素を扱う場合や、配列との相互変換を頻繁に行う場合は、実際にベンチマークを取って検証することをおすすめします。

よくある落とし穴

Set は便利ですが、仕様を知らないと思わぬ挙動に戸惑うことがあります。代表的な注意点を整理します。

  • オブジェクトは 参照で比較されるため、内容が同じでも別のオブジェクトであれば重複とは見なされません。
  • Set挿入順を保持しますが、配列のような インデックスアクセスはできません。番号で扱いたい場合は、配列に変換してから利用します。
  • WeakSet列挙できず、格納できるのは オブジェクトのみです。用途が限定される点に注意が必要です。
  • NaN は同じ値として扱われ、+0-0 も区別されません。これは Same-value-zero という比較ルールによる仕様です。

まとめ

Set は、重複を許さない値の集合を直感的に扱える便利なデータ構造です。配列の重複除去や高速な存在チェック、和集合・積集合といった集合的な処理を、シンプルで読みやすいコードで実装できます。

一方で、オブジェクトは参照で比較されるため、値の内容で同一性を判断したい場合は工夫が必要です。

こうした特性を理解したうえで使い分けることで、Set はコードの可読性と保守性を高める強力な選択肢になります。

YouTubeチャンネルでは、Visual Studio Codeを用いて上記の記事を見ながら確認できます。 ぜひYouTubeチャンネルもご覧ください。

YouTube Video