一意データの抽出とは何か
一意データの抽出は「重複している要素を取り除き、同じものが1回だけ現れる集合にする」処理です。ここが重要です:何を“同じ”とみなすかで手法が変わります。プリミティブ(数値・文字列)なら値一致、オブジェクトなら“IDやキーの一致”、複合条件なら“正規化したキー”で比較します。順序の扱い(先に出たものを残すか、後勝ちにするか)も実務上の重要ポイントです。
プリミティブの重複排除(Set が最短)
Set で一意化する
const nums = [1, 2, 2, 3, 1];
const unique = [...new Set(nums)]; // [1, 2, 3]
JavaScriptSet は同じ値を1つにまとめます。ここが重要です:順序は“最初に出現した順”で維持されます。NaN は同値として扱われ、-0 と 0 は同一視されます(多くの用途で問題になりません)。
filter+indexOf の古典パターン(理解用)
const unique = nums.filter((v, i, arr) => arr.indexOf(v) === i);
JavaScriptSet より遅くなることが多く、実務では 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 => !seen.has(u.id) && seen.add(u.id));
// [{ id:1, name:"Alice" }, { id:2, name:"Bob" }]
JavaScriptここが重要です:seen.add(...) は戻り値に Set を返すため、短く“チェック+登録”ができます。読みやすさを重視するなら分けて書いても構いません。
後勝ち(最後のものを残す)
const byId = new Map(users.map(u => [u.id, u]));
const unique = [...byId.values()];
// [{ id:1, name:"Alice v2" }, { id:2, name:"Bob" }]
JavaScriptMap は同じキーで上書きされるため、最終出現を採用します。ログや設定の“上書き結果”を得たいときに向きます。
任意のキーで一意化(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 uniqueSku = uniqueBy(products, p => p.sku.toLowerCase(), "first");
JavaScriptここが重要です:キー関数で“正規化(小文字化・トリム・変換)”を行えば、表記ゆれを吸収できます。実務では「同じ意味の値を同じキーにする」設計が肝心です。
文字列の表記ゆれ・正規化(大小・空白・全角半角)
正規化してから一意化する
const names = ["Alice", "alice ", "ALICE"];
const normalize = s =>
s.trim().toLowerCase().replace(/[A-Za-z0-9]/g, ch => String.fromCharCode(ch.charCodeAt(0) - 0xFEE0));
const unique = [...new Set(names.map(normalize))]; // ["alice"]
JavaScriptここが重要です:前処理で“同じものは同じキーへ”寄せると、重複排除の精度が上がります。ドメインに応じて、記号除去・NFC正規化なども検討します。
ネストやグループを跨いだ一意化(全体で重複排除)
グループ内のオブジェクトを横断して一意化
const groups = [
[{ id: 1, n: "A" }, { id: 2, n: "B" }],
[{ id: 2, n: "B v2" }, { id: 3, n: "C" }]
];
const seen = new Set();
const flat = groups.flat();
const unique = flat.filter(x => !seen.has(x.id) && seen.add(x.id));
// [{id:1,n:"A"}, {id:2,n:"B"}, {id:3,n:"C"}]
JavaScript外から来るネストデータはまず flat() で平坦化してから処理すると読みやすくなります。後勝ちにしたいなら Map でまとめ直します。
高度な同値判定(深い比較・構造キー)
JSON文字列をキーにする(簡易・限定的)
const items = [{a:1,b:2}, {b:2,a:1}, {a:1,b:3}];
const unique = [...new Map(items.map(x => [JSON.stringify(x), x])).values()];
// {a:1,b:2}(同じ構造)、{a:1,b:3}
JavaScriptここが重要です:プロパティ順序が違うと別物になります。キー順を揃える“カノニカル化”が必要な場合は、キーをソートしてから文字列化します。
const canon = obj => JSON.stringify(Object.fromEntries(Object.entries(obj).sort(([a],[b]) => a.localeCompare(b))));
const unique2 = [...new Map(items.map(x => [canon(x), x])).values()];
JavaScript深い比較はコストが高いため、可能なら“IDや正規化キー”を定義するほうが実務的です。
ストリーム・大規模データの一意化(メモリと性能)
逐次処理(見たものだけ記録)
function dedupeStream(iter, keyFn = x => x) {
const seen = new Set();
const out = [];
for (const x of iter) {
const k = keyFn(x);
if (!seen.has(k)) { seen.add(k); out.push(x); }
}
return out;
}
JavaScriptここが重要です:seen が大きくなるとメモリを食います。キー空間が巨大な場合は、期間別・シャーディング・Bloom filter(近似)などの戦略を検討します。まずは“必要な範囲だけ一意化する”スコープ設計が現実的です。
よくある落とし穴(重要ポイントの深掘り)
“同じ”の定義が曖昧
仕様で“何が同じか”を先に決めるのが最重要です。ID、大小無視、前後空白無視、全角半角、数値スケールなどを明文化してキー関数で反映します。
先勝ち・後勝ちの混乱
入力順で結果が変わります。ログや設定は“後勝ち”、履歴や最初の採用は“先勝ち”。プロダクトに合わせて明示的に選びましょう。
参照を壊す更新
一意化後に更新するならイミュータブルに(新インスタンスで置換)。辞書化(Object.fromEntries / Map)してから部分更新すると安全です。
深い比較の過コスト
JSONキーやディープイコールは重いです。実務では“比較キーを作る”方針に寄せると、性能・保守性ともに安定します。
まとめ
一意データの抽出は「Set/Map で重複を落とす」のが基本で、プリミティブは Set、オブジェクトは“IDやキー関数”で処理します。ここが重要です:同値の定義を先に決め、順序(先勝ち・後勝ち)を明示し、必要なら正規化で表記ゆれを吸収します。ネストは平坦化してから、広範囲は辞書化で O(1) に。深い比較は最終手段、性能と保守性を考え“比較キー設計”を優先する。これを押さえれば、初心者でも素早く正確に重複を排除し、実務で使える一意集合を作れます。
