はじめに
JavaScriptにおいて、データの中から重複を取り除き、ユニークな値のみを管理する必要が生じる場面は少なくありません。例えば、ユーザーからの入力値の正規化、タグやカテゴリの一覧表示、ログデータの集計時などが挙げられます。
このような要件を満たすデータ構造として、組み込みオブジェクトである Set が提供されています。
この記事では、Set の基本的な操作方法からユ-スケース、注意点など実際のコード例を交えて記載します。
Set とは?
Set は 重複のない値の集合 を保持するための組み込みオブジェクトです。
値の順序は保持されますが、重複する値は自動的に排除されます。
const set = new Set([1, 2, 3, 3, 2]);
console.log(set); // Set(3) { 1, 2, 3 }
上記のように、配列に重複があっても、Set に渡すとユニークな値だけが残ります。
基本的な操作方法
1. 値の追加・削除・存在確認
const fruits = new Set();
fruits.add("apple");
fruits.add("banana");
fruits.add("apple"); // 重複は無視される
console.log(fruits); // Set(2) {'apple', 'banana'}
console.log(fruits.has("banana")); // true
fruits.delete("banana");
console.log(fruits.has("banana")); // false
2. イテレーション(ループ)
Set は for...of や forEach でループ可能です。
const colors = new Set(["red", "green", "blue"]);
for (const color of colors) {
console.log(color);
}
// red
// green
// blue
Set が活躍する代表的なユースケース
1. 配列の重複を排除
🛠️ユースケース:チェックボックスで選ばれたカテゴリの送信前処理
チェックボックス形式でカテゴリを選択するフォームを作る場合、UIのバグやユーザー操作の繰り返しで、同じカテゴリが複数回選ばれてしまう可能性があります。
そのまま送信すると、APIに同じカテゴリIDが複数回送られてしまい、保存や集計の不具合につながることもあります。
Set を使えば簡単に重複を取り除いて送信できます。
const selectedCategoryIds = [1, 3, 2, 3, 1];
const uniqueCategoryIds = [...new Set(selectedCategoryIds)];
console.log(uniqueCategoryIds);
// => [1, 3, 2]
// 例: fetch('/api/update', { method: 'POST', body: JSON.stringify({ categoryIds: uniqueCategoryIds }) })
💡Setを使って嬉しいポイント
- 重複除去のロジックが1行で書けて可読性が高い
- ユーザーの意図しない重複送信によるサーバーエラーを防止
2. 大量データに対する存在チェック
🛠️ユースケース:ログイン中ユーザーが過去に閲覧した記事かを高速に判定する
「すでに閲覧済みの記事にはチェックマークを表示する」ようなUIを実装する場合、過去に閲覧した記事IDを保持し、その中に現在表示している記事IDが含まれているかを繰り返しチェックする必要があります。
Array.includes() では閲覧履歴が数千件以上になるとパフォーマンスに影響することがありますが、Set.has() なら高速です。
const viewedArticleIds = new Set([1001, 1002, 1003, 1004]);
const currentArticleId = 1002;
if (viewedArticleIds.has(currentArticleId)) {
console.log("✔ 閲覧済みマークを表示");
} else {
console.log("未閲覧");
}
💡Setを使って嬉しいポイント
- 数千〜数万件のデータでも即時判定が可能
- リアルタイムなUI更新やフィルタリング処理に適している
-
.has()はO(1)なのでループの中でも安心して使える
O(1) とは?
データの量が増えても、処理にかかる時間がほとんど変わらないということです。
-
Array.includes()の場合(O(N)に近いイメージ)- 過去の閲覧履歴がどれだけ多くても、一つずつ順番に確認していく必要があります
- 履歴が増えるほど、チェックにかかる時間も長くなります
-
Set.has()の場合(O(1)のイメージ)- 閲覧履歴がどれだけ多くても、目的の記事IDを「一瞬で」見つけられるようなものです
- データの量が増えても、確認にかかる時間はほとんど変わりません
このように、Set.has() は非常に高速に目的のデータがあるかを確認できるため、数千〜数万件のデータでも即時判定が可能でループの中でも安心して利用できます。
3. 集合演算(和・積・差)によるデータ比較・統合
🛠️ユースケース:ユーザーの権限変更画面で、追加・削除すべきロールを計算する
管理画面でユーザーのロールを更新するとき、「すでに持っているロール」と「新たに指定されたロール」の差分を使って、追加すべきロールと削除すべきロールを求める必要があります。
Set を使うと、重複や順序を気にせず論理的な集合演算ができ、無駄なく効率的に差分を計算できます。
const currentRoles = new Set(["viewer", "editor"]);
const updatedRoles = new Set(["admin", "editor"]);
// 削除するロール(現在はあるが、更新後はない)
const rolesToRemove = [...currentRoles].filter(
(role) => !updatedRoles.has(role)
);
console.log(rolesToRemove);
// => ["viewer"]
// 追加するロール(更新後にはあるが、現在はない)
const rolesToAdd = [...updatedRoles].filter(
(role) => !currentRoles.has(role)
);
console.log(rolesToAdd);
// => ["admin"]
💡Setを使って嬉しいポイント
- 簡潔な記述で差分の検出処理が書ける
- 並び順や重複を気にせず、論理的な一致だけに集中できる
- 和集合・積集合などの応用処理も同じ構文で実装可能
注意点
1. オブジェクトや配列は「同じ内容」でも別物と判定される
オブジェクトや配列などの参照型は、内容が同じでも別物とみなされる ことに注意しましょう。
const set = new Set();
set.add({ name: "Alice" });
set.add({ name: "Alice" });
console.log(set.size); // 2(同じ内容でも別のオブジェクト)
2. 順序を保証したい場合は配列を併用
Set の順序は「追加順」ですが、ソートは自動では行われません。
順序を厳密に制御したい場合は、Array.from(set).sort() などで対応する必要があります。
const scores = new Set([40, 10, 20]);
const sorted = [...scores].sort((a, b) => a - b);
console.log(sorted); // [10, 20, 40]
おわりに
JavaScript の Set は、配列処理や集合演算、重複排除において強力なツールです。
用途に応じて、Map や Array と適切に使い分けられるようになると、コードの効率性や可読性が向上します。