JavaScript | 配列・オブジェクト:実務パターン – 重複削除

JavaScript JavaScript
スポンサーリンク

重複削除とは何か

重複削除は「同じ値や同じレコードが複数含まれる配列から、重複を取り除き“1回だけ”にする」処理です。ここが重要です:何を“同じ”とみなすかで手法が変わります。プリミティブ(数値・文字列)は値一致、オブジェクトは“IDやキー”で一致を判定します。結果の順序(先に出たものを残すか、最後を残すか)を意識することも実務では重要です。


プリミティブの重複削除(Set が最短)

Set で一意化(先に出た順を維持)

const nums = [1, 2, 2, 3, 1];
const unique = [...new Set(nums)]; // [1, 2, 3]
JavaScript

Set は同じ値を1つにまとめ、最初に出現した順を保ちます。ここが重要です:NaN は同値として扱われ、-00 は同一視されます。これで困るケースは少ないですが、意味の違いが重要なら別の判定方法を選びます。

filter+indexOf の古典的書き方(理解用)

const unique = nums.filter((v, i, arr) => arr.indexOf(v) === i);
JavaScript

動作は分かりやすいものの、一般に Set より遅くなりがちです。実務では Set を優先するのが無難です。


オブジェクト配列の重複削除(IDで重複を落とす)

先勝ち(最初に出たレコードを残す)

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

const seen = new Set();
const unique = users.filter(u => {
  if (seen.has(u.id)) return false;
  seen.add(u.id);
  return true;
});
// [{ id:1, name:"Alice" }, { id:2, name:"Bob" }]
JavaScript

ここが重要です:先勝ちは“履歴の最初を尊重”したい場面に適します。ログの初出採用や、重複投入の早期レコードを優先したいときに向きます。

後勝ち(最後に出たレコードを残す)

const byId = new Map(users.map(u => [u.id, u]));
const unique = [...byId.values()];
// [{ id:1, name:"Alice v2" }, { id:2, name:"Bob" }]
JavaScript

同じ ID が後から来たら上書きされるため、最終状態を採用できます。設定や最終更新を反映したいときはこちらが適します。


任意キーでの重複削除(uniqueBy ユーティリティ)

キー関数で柔軟に“同値”を定義する

function uniqueBy(list, keyFn, keep = "first") {
  if (keep === "last") {
    const m = new Map(list.map(x => [keyFn(x), x]));
    return [...m.values()];
  }
  const seen = new Set();
  const out = [];
  for (const x of list) {
    const k = keyFn(x);
    if (!seen.has(k)) {
      seen.add(k);
      out.push(x);
    }
  }
  return out;
}

// 使い方
const products = [
  { sku: "A-001", name: "Pen" },
  { sku: "a-001", name: "Pen v2" },
  { sku: "B-010", name: "Book" }
];

const normalizeSku = s => s.trim().toLowerCase();
const uniqueSkuFirst = uniqueBy(products, p => normalizeSku(p.sku), "first");
const uniqueSkuLast  = uniqueBy(products, p => normalizeSku(p.sku), "last");
JavaScript

ここが重要です:キー関数で“表記ゆれ”を吸収(小文字化・トリム・全角半角変換)すれば、現実のデータに強い重複削除になります。仕様として“何を同じとみなすか”を明確化し、それを keyFn に反映します。


ネスト・グループ跨ぎの重複削除(平坦化して処理)

複数グループを横断して一意化

const groups = [
  [{ id: 1, n: "A" }, { id: 2, n: "B" }],
  [{ id: 2, n: "B v2" }, { id: 3, n: "C" }]
];

const flat = groups.flat();
const uniqueFirst = uniqueBy(flat, x => x.id, "first");
// 先勝ちの結果を得る
JavaScript

ここが重要です:ネスト配列はまず flat() で平坦化してから重複削除すると、意図が明確で保守しやすくなります。後勝ちにしたい場合は Map 方式や uniqueBy の “last” を使います。


高度な同値判定(構造キー・深い比較を使う場合)

構造を文字列キーにして重複削除(簡易)

const items = [{a:1,b:2}, {b:2,a:1}, {a:1,b:3}];
const canon = obj => JSON.stringify(Object.fromEntries(
  Object.entries(obj).sort(([ka],[kb]) => ka.localeCompare(kb))
));
const unique = [...new Map(items.map(x => [canon(x), x])).values()];
// {a:1,b:2} と {a:1,b:3} の一意化
JavaScript

ここが重要です:深い比較はコストが高いので、可能なら“IDや正規化キー”を用意する設計が望ましいです。プロパティ順差で別物にならないよう、キーをソートしてから文字列化します。


実務での安全設計(重要ポイントの深掘り)

同値の定義を先に決める

何を同じとみなすか(ID、大小無視、空白・全角半角、数値スケール)を仕様として明文化します。ここが曖昧だと結果が不安定になります。

先勝ち・後勝ちの選択を明示する

入力順で結果が変わるため、要件に合わせて「first か last か」を選び、コードに明示します。設定や上書きは後勝ち、履歴や初出は先勝ちが目安です.

参照の安全性(イミュータブルを徹底)

重複削除後の更新は“非破壊更新”で行います。スプレッドや map で新レコードを作り、辞書化(Object.fromEntries / Map)を併用すると部分更新が簡潔で安全です。

性能とメモリのバランス

Set/Map のサイズは重複が多いほど大きくなります。巨大データでは処理を分割する、ストリーム的に区間ごとに重複削除するなど、スコープ設計でメモリを抑えます。


すぐ使えるレシピ(短く安全な重複削除)

プリミティブの重複削除(先勝ち)

const dedupe = arr => [...new Set(arr)];
JavaScript

オブジェクトの重複削除(ID・先勝ち/後勝ち)

const dedupeByIdFirst = arr => {
  const seen = new Set();
  return arr.filter(x => !seen.has(x.id) && (seen.add(x.id), true));
};

const dedupeByIdLast  = arr => [...new Map(arr.map(x => [x.id, x])).values()];
JavaScript

任意キーで重複削除(表記ゆれ対応)

const dedupeBy = (arr, keyFn, keep = "first") => uniqueBy(arr, keyFn, keep);
// keyFn 例:s => s.trim().toLowerCase()
JavaScript

まとめ

重複削除は「Set/Map を使って、定義した“同値”に基づいて一意化する」ことが核心です。プリミティブは Set、オブジェクトは ID やキー関数で判定し、要件に応じて先勝ち・後勝ちを選ぶ。ネストは平坦化してから処理し、深い比較はコストが高いので“比較キーの設計”を優先する。イミュータブルを徹底し、性能はデータ規模に合わせて分割・辞書化で最適化する。これを押さえれば、初心者でも短く、正確で実務に耐える重複削除が書けます。

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