【JavaScript】データ重複を許さないSetクラスの紹介

はじめに

この記事の概要

こんにちは、株式会社TOKOSの杉田です!
今回はJavaScriptのSetクラスの基本的な使い方から、実践的な応用例まで詳しく解説していきます!
あまり使用する頻度はないかもしれませんが重複を許さないユニークな値の集合を扱う場合時などに活躍します!
ぜひ最後まで見ていってみてください!

この記事では、以下の内容について詳しく解説します:

  • Setクラスの使用方法

対象読者

  • 初学者の方
  • フロントエンド開発をされている方

この記事で扱う内容、扱わない内容

この記事で扱う内容 :

  • Setクラスの概要

この記事で扱わない内容:

  • JavaScriptの基本的な使用方法

概要

JavaScriptのSetクラスは、ES6(ECMAScript 2015)で導入されました!
冒頭でもお伝えしましたが、重複を許さないユニークな値の集合を扱う場合などで使用します!

Setクラスの基礎

Setオブジェクトは、プリミティブ値やオブジェクト参照を含む、どのような型のユニーク(一意)の集合も保持できます。
Setの主な特徴は下記になります。

  • 値が一度だけ格納される(重複不可)
  • 順序が保持される(挿入順)
  • キーを使わず値のみを格納
  • NaNとundefinedも格納可能

また、紛らわしいですが配列とは別物になります。
配列との違いは下記になります。

重複要素の扱い

  • Set
    • 重複する値を保持できない
  • 配列
    • 同じ値を複数持つことができる

要素の検索効率

  • Set
    • has()メソッドによる要素検索は非常に高速
  • 配列
    • includes()indexOf()による検索はSetに劣る

要素へのアクセス

  • Set
    • インデックスによる直接アクセスはできない
  • 配列
    • array[index]のように直接インデックスでアクセスできる

Setクラスの主要メソッド

Setクラスの主要メソッドは下記になります。

Setのメソッド配列での同等の操作説明
new Set()[]新しい空のコレクションを作成
add(value)push(value)要素を追加する
delete(value)splice(array.indexOf(value), 1)特定の値を削除する
has(value)includes(value) または indexOf(value) !== -1値が存在するか確認する
clear()length = 0 または splice(0)すべての要素を削除する
forEach(callback)forEach(callback)各要素に対してコールバックを実行
values()values()値のイテレータを返す
entries()entries()キーと値のペアのイテレータを返す(配列では[index, value])

基本的な使用例

要素の追加・削除などメソッドを使用した例は下記になります。

JavaScript
// 新しいSetを作成
const colors = new Set();

// 要素を追加
colors.add('赤');
colors.add('青');
colors.add('緑');

console.log(colors.size); // 3

// 重複要素の追加は無視される
colors.add('赤');
console.log(colors.size); // 3(変わらない)

// 要素の存在確認
console.log(colors.has('赤')); // true
console.log(colors.has('黄')); // false

// 要素の削除
colors.delete('青');
console.log(colors.size); // 2

// すべての要素を削除
colors.clear();
console.log(colors.size); // 0

上記例のように、重複要素が追加される場合は無視されます!

配列との相互変換

先ほど冒頭で配列とは別物と言いましたが、相互互換ができます!

JavaScript
// 配列からSetへの変換
const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueSet = new Set(array);
console.log(uniqueSet); // Set(5) {1, 2, 3, 4, 5}

// SetからArrayへの変換
const uniqueArray = [...uniqueSet];
console.log(uniqueArray); // [1, 2, 3, 4, 5]

// または Array.from() を使う方法
const anotherUniqueArray = Array.from(uniqueSet);
console.log(anotherUniqueArray); // [1, 2, 3, 4, 5]

実践的な応用例

ここから良く使うパターンを紹介します!

配列から重複を削除する

JavaScript
function removeDuplicates(array) {
  return [...new Set(array)];
}

const numbersWithDuplicates = [1, 2, 2, 3, 4, 4, 5, 5, 5];
const uniqueNumbers = removeDuplicates(numbersWithDuplicates);
console.log(uniqueNumbers); // [1, 2, 3, 4, 5]

2つの配列の共通要素を見つける(交差)

JavaScript
function intersection(arrayA, arrayB) {
  const setB = new Set(arrayB);
  return arrayA.filter(element => setB.has(element));
}

const array1 = [1, 2, 3, 4, 5];
const array2 = [3, 4, 5, 6, 7];
console.log(intersection(array1, array2)); // [3, 4, 5]

2つの配列の差分を見つける(差集合)

JavaScript
function difference(arrayA, arrayB) {
  const setB = new Set(arrayB);
  return arrayA.filter(element => !setB.has(element));
}

const array1 = [1, 2, 3, 4, 5];
const array2 = [3, 4, 5, 6, 7];
console.log(difference(array1, array2)); // [1, 2]

2つの配列を結合して重複を削除(和集合)

JavaScript
function union(arrayA, arrayB) {
  return [...new Set([...arrayA, ...arrayB])];
}

const array1 = [1, 2, 3];
const array2 = [2, 3, 4, 5];
console.log(union(array1, array2)); // [1, 2, 3, 4, 5]

オブジェクト配列から特定のプロパティの重複を削除

JavaScript
function getUniqueByProperty(array, prop) {
  const seen = new Set();
  return array.filter(item => {
    const value = item[prop];
    if (seen.has(value)) {
      return false;
    }
    seen.add(value);
    return true;
  });
}

const users = [
  { id: 1, name: '田中' },
  { id: 2, name: '鈴木' },
  { id: 3, name: '田中' }, // 重複した名前
  { id: 4, name: '佐藤' }
];

const uniqueUsers = getUniqueByProperty(users, 'name');
console.log(uniqueUsers);
// [{ id: 1, name: '田中' }, { id: 2, name: '鈴木' }, { id: 4, name: '佐藤' }]

パフォーマンスの考慮点

Setは配列操作よりも(大量のデータを扱う際は)パフォーマンスが良いです。
大量のデータを扱う場合、配列のincludes()よりもSetのhas()の方が高速です。
下記コードは配列と比べた際の結果になります。
結果は100分の1程度になります。

JavaScript
// パフォーマンス比較の例
const largeArray = Array.from({ length: 100000 }, (_, i) => i);
const largeSet = new Set(largeArray);

console.time('Array includes');
const inArray = largeArray.includes(99999);
console.timeEnd('Array includes'); // Array includes: ~5-10ms

console.time('Set has');
const inSet = largeSet.has(99999);
console.timeEnd('Set has'); // Set has: ~0.01-0.05ms

さいごに

今回はあまり馴染み深くないSetクラスについて紹介しました!
配列を使用することが多いとは思いますが、扱うデータによっては簡単に実装できたりパフォーマンスに影響が出るためなるべくSetを使いたいです!