JavaScript Tips | 配列ユーティリティ:重複抽出

JavaScript JavaScript
スポンサーリンク

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

ここでの「重複抽出」は、配列の中で「1 回しか出てこないもの」ではなく、「2 回以上出てくるもの」を取り出す処理です。

重複削除(ユニーク化)は「かぶりを消す」でしたが、
重複抽出は「どれがかぶっているかをあぶり出す」イメージです。

例えばこうです。

// 元の配列
[1, 1, 2, 3, 3, 3]

// 重複抽出の結果(どの値が重複しているか)
[1, 3]
JavaScript

業務だと、

  • 入力データの中で「同じ ID が複数回出てきていないか」をチェックしたい
  • CSV の中で「重複コード」を検出してエラーにしたい

といった場面でよく使います。


一番シンプルな重複抽出:プリミティブ値の場合

出現回数を数えて「2 回以上」のものを取る

まずは、数値や文字列などのプリミティブ値だけの配列を対象にします。

やりたいことはシンプルで、

  1. 各値が何回出てきたかを数える
  2. 出現回数が 2 回以上の値だけを集める

です。

実装例:count → filter

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

  const counts = new Map();

  for (const value of array) {
    const current = counts.get(value) ?? 0;
    counts.set(value, current + 1);
  }

  const duplicates = [];

  for (const [value, count] of counts.entries()) {
    if (count > 1) {
      duplicates.push(value);
    }
  }

  return duplicates;
}
JavaScript

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

Map で「値ごとのカウント」を持つ

const counts = new Map();

for (const value of array) {
  const current = counts.get(value) ?? 0;
  counts.set(value, current + 1);
}
JavaScript

ここでやっているのは、「値 → 出現回数」の表を作ることです。

counts.get(value) で今までの回数を取り出し、
なければ 0 とみなして +1 して、set し直しています。

例えば [1, 1, 2, 3, 3, 3] を通すと、最終的にこうなります。

  • 1 → 2 回
  • 2 → 1 回
  • 3 → 3 回

「2 回以上」のものだけを抽出する

const duplicates = [];

for (const [value, count] of counts.entries()) {
  if (count > 1) {
    duplicates.push(value);
  }
}
JavaScript

ここで、「出現回数が 2 回以上」の値だけを duplicates に入れています。

結果として、「どの値が重複しているか」の一覧が取れます。

実際の動き

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

findDuplicatesPrimitive(["A", "B", "A", "C", "B"]);
// ["A", "B"]

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

findDuplicatesPrimitive([]);
// []
JavaScript

「重複している要素そのもの」を取り出したい場合

値ではなく「配列の要素」を返す版

さっきの関数は「重複している値の一覧」でしたが、
「重複している行(要素)を全部取りたい」こともあります。

例えば、

[1, 1, 2, 3, 3, 3]
JavaScript

から、

[1, 1, 3, 3, 3]
JavaScript

のように、「重複しているものだけを抜き出す」イメージです。

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

  const counts = new Map();

  for (const value of array) {
    const current = counts.get(value) ?? 0;
    counts.set(value, current + 1);
  }

  const result = [];

  for (const value of array) {
    if ((counts.get(value) ?? 0) > 1) {
      result.push(value);
    }
  }

  return result;
}
JavaScript

実際の動き

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

extractDuplicateElementsPrimitive(["A", "B", "A", "C", "B"]);
// ["A", "A", "B", "B"]
JavaScript

「どの値が重複しているか」だけ知りたいなら findDuplicatesPrimitive
「重複している行を全部取りたい」なら extractDuplicateElementsPrimitive
という使い分けができます。


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

「id が重複しているレコード」を見つけたい

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

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

ここで、「id が重複しているレコードだけを抽出したい」という要件はかなり頻出です。

uniqueByKey の「重複版」:findDuplicatesByKey

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

  const counts = new Map();

  for (const item of array) {
    if (item == null || typeof item !== "object") {
      continue;
    }
    const value = item[key];
    const current = counts.get(value) ?? 0;
    counts.set(value, current + 1);
  }

  const result = [];

  for (const item of array) {
    if (item == null || typeof item !== "object") {
      continue;
    }
    const value = item[key];
    if ((counts.get(value) ?? 0) > 1) {
      result.push(item);
    }
  }

  return result;
}
JavaScript

重要なポイント

1 回目のループで「キーの値ごとの出現回数」を数え、
2 回目のループで「2 回以上出てくるキーを持つ要素だけ」を結果に入れています。

実際の動き

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

findDuplicatesByKey(users, "id");
// [
//   { id: 1, name: "Alice" },
//   { id: 1, name: "Alice (duplicate)" },
//   { id: 3, name: "Carol" },
//   { id: 3, name: "Carol (duplicate)" },
// ]
JavaScript

これで、「重複している id を持つレコードだけ」を一気に取り出せます。


任意の「キー関数」で重複抽出する

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

例えば、「codeversion の組み合わせが重複している行を抽出したい」場合、
単純な key 文字列では足りません。

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

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

  const counts = new Map();

  for (const item of array) {
    const key = keyFn(item);
    const current = counts.get(key) ?? 0;
    counts.set(key, current + 1);
  }

  const result = [];

  for (const item of array) {
    const key = keyFn(item);
    if ((counts.get(key) ?? 0) > 1) {
      result.push(item);
    }
  }

  return result;
}
JavaScript

実際の使い方

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

const duplicates = findDuplicatesBy(
  items,
  (item) => `${item.code}:${item.version}`
);

// 結果
// [
//   { code: "A", version: 1 },
//   { code: "A", version: 1 },
//   { code: "B", version: 1 },
//   { code: "B", version: 1 },
// ]
JavaScript

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


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

「重複削除」と「重複抽出」をセットで考える

重複削除(ユニーク化)と重複抽出は、よくセットで使います。

  • 重複削除 → 正常系の処理(ユニークな一覧を作る)
  • 重複抽出 → 異常系の検知(入力におかしな重複がないかチェックする)

例えば、CSV で「商品コード一覧」を受け取るとき、

  1. findDuplicatesByKey で「重複コード」がないかチェックする
  2. 問題なければ uniqueByKey でユニークな一覧を作る

という流れにすると、「入力チェック」と「正常処理」がきれいに分かれます。

「何をもって重複とみなすか」を明文化する

重複抽出は、「同じとは何か?」をはっきりさせる作業です。

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

これをユーティリティの関数名や引数(keykeyFn)で表現しておくと、
あとからコードを読んだ人にも意図が伝わりやすくなります。

エラー表示やログと組み合わせる

重複抽出は、「そのまま使う」だけでなく、「エラーの材料」としても使えます。

const duplicates = findDuplicatesByKey(users, "id");
if (duplicates.length > 0) {
  const ids = Array.from(new Set(duplicates.map((u) => u.id)));
  console.error("重複しているIDがあります:", ids);
  // ここでバリデーションエラーとして返す、など
}
JavaScript

こうしておけば、「どの ID が重複しているか」をユーザーに返したり、
ログに残したりするのも簡単です。


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

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

findDuplicatesPrimitive([1, 1, 2, 3, 3, 3]);
extractDuplicateElementsPrimitive([1, 1, 2, 3, 3, 3]);

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

findDuplicatesByKey(users, "id");

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

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

「どの値・どの要素が“重複しているもの”として抽出されているか」を、
自分の目で確認してみてください。

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

export function findDuplicatesPrimitive(...) { ... }
export function extractDuplicateElementsPrimitive(...) { ... }
export function findDuplicatesByKey(...) { ... }
export function findDuplicatesBy(...) { ... }
JavaScript

のような関数を置き、

「配列の重複をチェックしたくなったら、必ずこの“重複抽出ユーティリティ”を通す」

というルールを作ってみてください。
それだけで、あなたの配列バリデーションは、場当たり的な for 文から、
意図と一貫性を備えた「業務レベルの検証ロジック」に変わっていきます。

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