JavaScript Tips | 配列ユーティリティ:重複削除

JavaScript JavaScript
スポンサーリンク

何をしたいユーティリティか:「配列の重複削除」

ここでの「重複削除」は、同じ値が何度も入っている配列から、重複を取り除いて「一意な要素だけの配列」を作る処理です。

業務だと、こんな場面でよく出てきます。

  • ユーザーが選んだ ID の一覧から、重複を消したい。
  • CSV から読み込んだコード一覧をユニークにしたい。
  • ログや集計で「種類の数」だけ知りたい。

毎回手書きでやるのではなく、「重複削除ユーティリティ」を 1 個決めておくと、コードが一気にスッキリします。


一番シンプルな重複削除:Set を使う

Set を使った基本形

プリミティブ値(数値・文字列・真偽値など)だけの配列なら、Set を使うのが一番簡単です。

function uniquePrimitive(array) {
  if (!Array.isArray(array)) {
    return [];
  }

  return Array.from(new Set(array));
}
JavaScript

重要ポイントをかみ砕いて説明する

Set は「同じ値を 2 回入れても 1 回分しか持たない」コレクションです。

const s = new Set();
s.add(1);
s.add(1);
s.add(2);
s; // Set { 1, 2 }
JavaScript

これをそのまま配列に戻すと、「重複のない配列」になります。

Array.from(new Set([1, 1, 2, 3, 3]));
// [1, 2, 3]
JavaScript

uniquePrimitive では、
「配列かどうかチェック → Set に通す → 配列に戻す」という流れを 1 行にまとめています。

実際の動き

uniquePrimitive([1, 1, 2, 3, 3]);
// [1, 2, 3]

uniquePrimitive(["a", "b", "a", "c"]);
// ["a", "b", "c"]

uniquePrimitive([true, false, true]);
// [true, false]

uniquePrimitive("not array");
// []
JavaScript

ポイント:
Set は「値の同一性」で重複を判定しますが、オブジェクトや配列は「参照」が違えば別物として扱われます。
つまり、{ id: 1 } が 2 つあっても、Set 的には「別の要素」です。
オブジェクトの重複削除は、もう一段工夫が必要です。


オブジェクト配列の重複削除:キーを指定する

「id が同じなら同じもの」とみなしたい

業務では、こんな配列がよく出てきます。

const users = [
  { id: 1, name: "Alice" },
  { id: 1, name: "Alice (duplicate)" },
  { id: 2, name: "Bob" },
];
JavaScript

ここで、「id が同じなら重複とみなして 1 件にしたい」という要件はよくあります。
この場合は、「どのプロパティを重複判定に使うか」をユーティリティに教えてあげる必要があります。

key を指定する uniqueByKey

function uniqueByKey(array, key) {
  if (!Array.isArray(array)) {
    return [];
  }

  const seen = new Set();
  const result = [];

  for (const item of array) {
    if (item == null || typeof item !== "object") {
      continue;
    }

    const value = item[key];

    if (seen.has(value)) {
      continue;
    }

    seen.add(value);
    result.push(item);
  }

  return result;
}
JavaScript

uniqueByKey の重要ポイントを深掘りする

どの値で「見たことあるか」を判定するか

const seen = new Set();
const result = [];
JavaScript

seen は「すでに出てきたキーの値」を覚えておくための Set です。
result は「重複を取り除いた結果配列」です。

配列を 1 件ずつ見ていく

for (const item of array) {
  if (item == null || typeof item !== "object") {
    continue;
  }

  const value = item[key];

  if (seen.has(value)) {
    continue;
  }

  seen.add(value);
  result.push(item);
}
JavaScript

ここでやっていることはシンプルです。

  1. item がオブジェクトでなければスキップ。
  2. item[key] を取り出す。
  3. その値が seen にすでにあれば「重複」とみなしてスキップ。
  4. なければ seen に登録し、result に追加。

これで、「指定したキーの値が同じものは 1 回だけ残す」動きになります。

実際の動き

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

uniqueByKey(users, "id");
// [
//   { id: 1, name: "Alice" },
//   { id: 2, name: "Bob" },
// ]
JavaScript

最初に出てきた id: 1 の要素だけが残り、
2 回目の id: 1 はスキップされています。


任意の「キー関数」で重複判定する

「複数項目の組み合わせ」で重複を決めたい場合

例えば、「codeversion の組み合わせが同じなら重複」としたい場合、
単純な key 文字列では足りません。

そのときは、「要素から重複判定用のキーを作る関数」を渡せるようにします。

function uniqueBy(array, keyFn) {
  if (!Array.isArray(array)) {
    return [];
  }

  const seen = new Set();
  const result = [];

  for (const item of array) {
    const key = keyFn(item);

    if (seen.has(key)) {
      continue;
    }

    seen.add(key);
    result.push(item);
  }

  return result;
}
JavaScript

実際の使い方

const items = [
  { code: "A", version: 1 },
  { code: "A", version: 1 },
  { code: "A", version: 2 },
  { code: "B", version: 1 },
];

const unique = uniqueBy(items, (item) => `${item.code}:${item.version}`);
// [
//   { code: "A", version: 1 },
//   { code: "A", version: 2 },
//   { code: "B", version: 1 },
// ]
JavaScript

keyFn の中で、
「重複判定に使いたい情報」を文字列などにまとめて返してあげれば OK です。


実務で意識してほしい設計のポイント

「何をもって同じとみなすか」をはっきりさせる

重複削除は、「同じとは何か?」を決める作業でもあります。

  • プリミティブ配列なら「値が同じなら同じ」。
  • オブジェクト配列なら「id が同じなら同じ」。
  • 場合によっては「code+version の組み合わせが同じなら同じ」。

これを曖昧にしたまま書き始めると、
あとから「このケースは別扱いにしたい」といった例外が増えていきます。

ユーティリティとして、

uniquePrimitive
uniqueByKey
uniqueBy
JavaScript

のように「ルールごとに関数を分ける」と、
コードを読む人にも意図が伝わりやすくなります。

順序をどう扱うか

ここまでの実装は、「最初に出てきた要素を残し、後から出てきた重複を捨てる」動きです。
これは多くの業務では自然な挙動ですが、
「最後に出てきたものを優先したい」ケースもあります。

その場合は、ループを逆順に回すか、
Map を使って「キー → 要素」を上書きしていく実装に変える、などの工夫が必要です。

パフォーマンスを意識するタイミング

SetMap を使った重複削除は、
基本的に「配列の長さに比例する時間」で終わります(O(n))。

業務で扱う配列が数千〜数万件程度なら、
ほとんどの場合これで十分です。

もし数十万〜数百万件レベルになってきたら、

  • そもそもそんなに一度にメモリに載せる設計でよいのか
  • サーバ側でユニークにしてから渡せないか

といった、もう一段上の設計を見直すタイミングです。


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

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

uniquePrimitive([1, 1, 2, 3, 3]);
uniquePrimitive(["a", "b", "a", "c"]);

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

uniqueByKey(users, "id");

const items = [
  { code: "A", version: 1 },
  { code: "A", version: 1 },
  { code: "A", version: 2 },
  { code: "B", version: 1 },
];

uniqueBy(items, (item) => `${item.code}:${item.version}`);
JavaScript

「どの要素が残り、どの要素が捨てられているか」を自分の目で確認してみてください。

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

export function uniquePrimitive(...) { ... }
export function uniqueByKey(...) { ... }
export function uniqueBy(...) { ... }
JavaScript

を置き、

「配列の重複を消したくなったら、必ずこの“重複削除ユーティリティ”を通す」

というルールを作ってみてください。
それだけで、あなたの配列処理は、場当たり的な for 文の寄せ集めから、
意図と一貫性を備えた「業務レベルの配列ユーティリティ」に変わっていきます。

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