JavaScript Tips | 配列ユーティリティ:Set 変換

JavaScript JavaScript
スポンサーリンク

何をしたいユーティリティか:「Set 変換」

ここでの「Set 変換」は、配列を Set に変換するユーティリティのことです。
一言でいうと「重複をなくしたい」「含まれているかを高速に調べたい」ときに使う道具です。

配列のままだと「この値が入っているか?」を調べるのに includes を毎回回す必要がありますが、
Set にしておけば set.has(value) で一発で判定できます。
また、Set は同じ値を二重に持たないので、「ユニークな値の集合」を作るのにも向いています。


一番基本:配列を Set に変換する

素朴な書き方とユーティリティ化

JavaScript では、配列から Set を作るのはとても簡単です。

const set = new Set(array);
JavaScript

これだけで、「配列の要素を全部突っ込んだ Set」ができます。
ただ、業務コードでは次のようなことを毎回気にするのが面倒になります。

配列じゃないものが来たらどうするか。
nullundefined が来たらどうするか。
「ユニークな配列に戻したい」ときに毎回同じ書き方をするのがだるい。

なので、まずは「安全に Set に変換する」ユーティリティを用意します。

function toSet(array) {
  if (!Array.isArray(array)) {
    return new Set();
  }
  return new Set(array);
}
JavaScript

これで、「配列じゃないものが来ても、とりあえず空の Set を返す」という安全な入り口ができます。


Set 変換の一番よくある用途:重複削除(ユニーク化)

Set にしてから配列に戻す

「配列から重複を消したい」というのは、業務でめちゃくちゃよく出てきます。
Set は「同じ値を二重に持たない」性質があるので、これを利用してユニーク配列を作れます。

function unique(array) {
  if (!Array.isArray(array)) {
    return [];
  }
  return [...new Set(array)];
}
JavaScript

ここでの重要ポイントは、new Set(array) で重複を消し、
[...](スプレッド構文)でまた配列に戻していることです。

動作例

const ids = [1, 2, 2, 3, 1];

const uniqIds = unique(ids);
// [1, 2, 3] (順番は元の出現順)
JavaScript

「重複を消したい」ときに、毎回 filter とか indexOf を駆使するより、
「Set にして戻す」というパターンをユーティリティにしておくほうが、読みやすくて安全です。


含まれているかを高速に調べる:membership 用 Set

配列の includes と Set の has の違い

配列で「この値が入っているか?」を調べるときは、普通こう書きます。

const exists = array.includes(value);
JavaScript

これは内部的には「先頭から順に見ていく」ので、要素数が増えるとだんだん重くなります。
一方、Sethas は「集合に含まれているか」を調べるための仕組みで、
多くのケースで配列より高速に判定できます。

事前に Set に変換しておく

「同じ配列に対して、何度も membership チェックをする」ような場面では、
先に Set に変換しておくと効率が良くなります。

const allowedIdsArray = [1, 2, 3, 5, 8, 13];
const allowedIdsSet = toSet(allowedIdsArray);

function isAllowed(id) {
  return allowedIdsSet.has(id);
}
JavaScript

ここでの重要ポイントは、「チェックのたびに includes するのではなく、
一度 Set を作って、それを何度も使い回す」という発想です。


オブジェクト配列での Set 変換:キーだけを Set にする

そのままでは「同じオブジェクトかどうか」になってしまう

オブジェクトの配列をそのまま new Set(array) に入れると、
「同じ内容かどうか」ではなく「同じ参照かどうか」で重複判定されます。

const a = { id: 1 };
const b = { id: 1 };

const set = new Set([a, b]);
set.size; // 2(中身は同じでも別オブジェクトなので別扱い)
JavaScript

業務でやりたいのはたいてい「id が同じなら同じものとみなしたい」なので、
オブジェクトそのものではなく「キーだけ」を Set にするのが実用的です。

キーの Set を作るユーティリティ

function toKeySet(array, keySelector) {
  if (!Array.isArray(array) || typeof keySelector !== "function") {
    return new Set();
  }

  const set = new Set();
  array.forEach((item, index) => {
    const key = keySelector(item, index);
    if (key != null) {
      set.add(key);
    }
  });
  return set;
}
JavaScript

ここでの重要ポイントは、「何をキーとみなすか」を keySelector で決めていることです。

動作例

const users = [
  { id: 1, name: "Taro" },
  { id: 2, name: "Hanako" },
  { id: 1, name: "Taro (duplicate)" },
];

const idSet = toKeySet(users, (u) => u.id);
// Set { 1, 2 }

idSet.has(1); // true
idSet.has(3); // false
JavaScript

「ユーザーの id の集合」を簡単に作れるので、
「この id は既に存在するか?」といったチェックが楽になります。


Set 変換を使った、よくある業務パターン

例1:二つの配列の共通要素を取りたい

例えば、「ユーザーが選択した ID」と「システムが許可している ID」の共通部分だけを取りたいケース。

const selected = [1, 2, 3, 4];
const allowed = [2, 4, 6];

const allowedSet = toSet(allowed);

const validSelected = selected.filter((id) => allowedSet.has(id));
// [2, 4]
JavaScript

ここでのポイントは、「片方を Set にしておいて、もう片方を filter で絞る」というパターンです。
これも「Set 変換」があるからこそ書きやすくなります。

例2:重複を除いた ID リストを作る

const rawIds = [1, 2, 2, 3, 1, 4];

const uniqIds = unique(rawIds);
// [1, 2, 3, 4]
JavaScript

「DB に渡す ID リストは重複なしにしたい」といった場面で、そのまま使えます。

例3:一度見たことがあるかどうかを記録する

「このユーザーには一度だけ通知を送りたい」といったケースで、
「既に通知済みの ID の Set」を持っておく、という使い方もよくします。

const notifiedIds = new Set();

function notifyOnce(userId) {
  if (notifiedIds.has(userId)) {
    return; // もう通知済み
  }
  notifiedIds.add(userId);
  // 通知処理を書く
}
JavaScript

ここではユーティリティというより「Set そのものの使い方」ですが、
「配列 → Set 変換」を知っていると、こういう設計も自然に思いつくようになります。


Set 変換ユーティリティで意識してほしいポイント

1. 「配列じゃないもの」が来ても壊れないようにする

toSetunique は、必ず「配列かどうか」をチェックしてから処理しています。
現実の業務コードでは、「本当は配列のはずだけど、何かの拍子に null やオブジェクトが来る」ことが普通にあります。

そこで落ちると、「たまにだけ落ちる謎のバグ」になります。
なので、ユーティリティ側で「配列じゃなかったら空を返す」と決めておくのは、とても大事です。

2. 「Set にするだけ」と「ユニーク配列に戻す」を分けて考える

用途としては、大きく二つあります。

Set にして membership チェックに使う。
Set にして重複を消し、配列に戻す(ユニーク化)。

前者は toSet、後者は unique のように、
目的ごとに関数を分けておくと、コードの意図がはっきりします。

3. オブジェクト配列は「キーの Set」を作る

オブジェクト配列をそのまま new Set に入れても、
「同じ id なら同じ」とは判定してくれません。

業務でやりたいのはたいてい「id の集合」「コードの集合」なので、
toKeySet のように「キーだけを Set にする」ユーティリティを用意しておくと、とても使いやすくなります。


手を動かして感覚をつかむ

コンソールで、次のようなコードを実際に打ってみてください。

function toSet(array) {
  if (!Array.isArray(array)) return new Set();
  return new Set(array);
}

function unique(array) {
  if (!Array.isArray(array)) return [];
  return [...new Set(array)];
}

function toKeySet(array, keySelector) {
  if (!Array.isArray(array) || typeof keySelector !== "function") {
    return new Set();
  }
  const set = new Set();
  array.forEach((item, index) => {
    const key = keySelector(item, index);
    if (key != null) {
      set.add(key);
    }
  });
  return set;
}

const ids = [1, 2, 2, 3, 1];
const uniqIds = unique(ids);

const users = [
  { id: 1, name: "Taro" },
  { id: 2, name: "Hanako" },
  { id: 1, name: "Taro (dup)" },
];

const idSet = toKeySet(users, (u) => u.id);
JavaScript

uniqIdsidSet.has(1) の結果を見て、「Set に変換すると何が嬉しいのか」を自分の目で確かめてみてください。

そのうえで、自分のプロジェクトに

export function toSet(...) { ... }
export function unique(...) { ... }
export function toKeySet(...) { ... }
JavaScript

のような関数を置き、「配列を“集合”として扱いたくなったら、必ずこの“Set 変換ユーティリティ”を通す」と決めてみてください。
それだけで、「なんとなく配列で頑張るコード」から、「集合を意識した一段大人な設計」に近づいていきます。

タイトルとURLをコピーしました