JavaScript | ES6+ 文法:新データ構造 – Set による重複排除

JavaScript JavaScript
スポンサーリンク

Set で重複排除とは何か(まずイメージから)

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

この「同じ値は 1 つだけ」という性質を利用すると、

配列に同じ値が何個も入っていても
「かんたんに重複を取り除いた配列」に変換できます。

結論から言うと、こう書きます。

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

const unique = [...new Set(nums)];

console.log(unique); // [1, 2, 3, 4]
JavaScript

new Set(nums) で「重複なし集合」にして、
[...] でまた配列に戻している、という動きです。

ここが重要です。
「配列の重複を消したい」と思ったら、「for や if で頑張る前に Set を思い出す」
これが ES6 以降の鉄板パターンです。

Set による重複排除の基本パターン

配列 → Set → 配列 の 3 ステップ

動きを分解するとこうなります。

  1. 配列から Set を作る
  2. Set の中で自動的に重複が消える
  3. Set を配列に戻す

コードで順番に書くと、こういう形です。

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

// 1. 配列から Set を作る(この時点で重複はない集合になる)
const set = new Set(nums);

// 2. Set をスプレッド構文で展開して、
// 3. [] の中に入れ直すことで「新しい配列」にする
const unique = [...set];

console.log(unique); // [1, 2, 3, 4]
JavaScript

1 行で書くと、こう。

const unique = [...new Set(nums)];
JavaScript

「Set は重複を許さない」
「スプレッド構文 ... は反復可能なものを展開する」
この 2 つを組み合わせた、ES6 らしいテクニックです。

文字列でも同じように使える

数値だけでなく、文字列にもそのまま使えます。

const names = ["Alice", "Bob", "Alice", "Charlie", "Bob"];

const uniqueNames = [...new Set(names)];

console.log(uniqueNames); // ["Alice", "Bob", "Charlie"]
JavaScript

書き味を体で覚える

何度も繰り返しますが、形をそのまま覚えてしまって大丈夫です。

const unique = [...new Set(array)];
JavaScript

「配列 → Set → 配列」という変換だと頭で理解しつつ、
指はこのワンライナーを書けるようにしておく、とても価値があります。

なぜ Set で重複が消えるのか(ここを深掘り)

Set は「同じ値を 2 回 add しても 1 回分だけ」持つ

Set を単体で使うと、こういう動きでした。

const s = new Set();

s.add(1);
s.add(2);
s.add(2); // 2 回目は無視される
s.add(3);
s.add(3);
s.add(3); // 全部無視

console.log(s);      // Set(3) { 1, 2, 3 }
console.log(s.size); // 3
JavaScript

「すでにある値なら、増えない」という性質が標準でついています。

この性質が、配列から Set を作ったときにもそのまま効きます。

const nums = [1, 2, 2, 3, 3, 3, 4];
const s = new Set(nums); // add を順番にやっているイメージ

console.log(s); // Set(4) { 1, 2, 3, 4 }
JavaScript

何をもって「同じ」と判断しているか

Set は、内部的には ===(厳密等価)で見ていると考えてOKです。

数値や文字列などのプリミティブ値なら、「値そのもの」が同じなら同値とみなします。

const s = new Set();

s.add(1);
s.add(1);    // 同じ数値 → 重複扱い
s.add("1");  // 文字列 "1" は別物

console.log(s); // Set(2) { 1, '1' }
JavaScript

オブジェクトや配列の場合は、「中身」ではなく「同じ参照かどうか」で判断します。

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

const s = new Set();

s.add(a);
s.add(b);

console.log(s.size); // 2(別オブジェクトなので両方入る)
JavaScript

ここが重要です。
オブジェクトを Set で重複排除したいときは、「同じオブジェクトを 2 回入れたら重複として扱われる」のであって、「中身が同じ別オブジェクト」は別物として扱われる
プリミティブ値の重複排除と、オブジェクトの重複排除は感覚が違うので注意してください。

Set を使わない書き方との比較(違いを体感する)

古い発想:for と includes で頑張る

Set がなかった頃、よくこう書いていました。

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

const unique = [];

for (const n of nums) {
  if (!unique.includes(n)) {
    unique.push(n);
  }
}

console.log(unique); // [1, 2, 3, 4]
JavaScript

分かりやすいですが、

  • unique.includes で毎回線形検索
  • 「含まれていなければ push」ロジックを毎回書く

と、処理もコードも少し重いです。

ES6 の発想:Set に丸投げ

同じことを Set で書くと、こうなります。

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

const unique = [...new Set(nums)];
JavaScript

コードが短くなるだけでなく、
「やりたいこと」が Set とワンライナーに集約されて、意図が伝わりやすくなっています。

ここが重要です。
Set を使うことで、自分のコードから「重複チェックのロジック」を追い出せる
「重複を気にする」ところを Set に全部任せて、自分はロジックの本質だけを書く、という発想です。

応用:重複排除+他の変換を同時にやる

重複排除してからソートする

const nums = [3, 1, 2, 3, 2, 1, 5];

const uniqueSorted = [...new Set(nums)].sort((a, b) => a - b);

console.log(uniqueSorted); // [1, 2, 3, 5]
JavaScript

「重複排除 → 昇順ソート」が 1 行で書けます。

重複排除してからフィルタする

const nums = [1, 2, 2, 3, 3, 3, 4, 5];

const uniqueEven = [...new Set(nums)].filter((n) => n % 2 === 0);

console.log(uniqueEven); // [2, 4]
JavaScript

オブジェクト配列の重複排除(キーを基準に)

オブジェクトそのものは Set で簡単に「中身の重複」を判定できないので、
「特定のキーの重複を避けたい」場合は Map や Set と組み合わせます。

例えば、「id が重複しているユーザーを 1 つにまとめたい」ケース。

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

const seenIds = new Set();
const uniqueUsers = [];

for (const user of users) {
  if (seenIds.has(user.id)) continue;

  seenIds.add(user.id);
  uniqueUsers.push(user);
}

console.log(uniqueUsers);
// id が 1 のものは最初の 1 つだけ残る
JavaScript

ここが重要です。
「オブジェクト配列の重複排除」は、直接 Set に配列を渡しても望んだ結果にならないことが多い
「何をもって重複とみなすか?」(id?name?全フィールド?)をはっきりさせて、そのキーを Set に入れて判定します。

注意点と「ここまで知っていれば十分」なライン

Set で順番はどうなる?

new Set(array) でできる Set は、元の配列の「登場順」を保ちます。

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

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

最初に出てきた 3 → 1 → 2 の順番が残っています。
順序が大事な場合も、Set を使って大丈夫です。

元の配列は変更されない(イミュータブルに扱える)

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

const unique = [...new Set(nums)];

console.log(nums);   // [1, 2, 2, 3]
console.log(unique); // [1, 2, 3]
JavaScript

元の配列を壊さず、「重複を取った新しい配列」を作っているだけです。
関数型っぽく、安全に扱えます。

性能面でも素直に「Set に任せて OK」

重複数が多かったり、配列が大きくなったりしても、
Set はハッシュ構造を使って効率よく存在チェックをしてくれます。

「パフォーマンスが心配だから for で自前実装しよう」と悩む前に、
まずは Set でシンプルに書くのがおすすめです。

まとめ

Set による重複排除の本質は、
「Set の“同じ値は 1 つだけ”という性質を、配列に一瞬だけ借りる」 ことです。

押さえておきたいポイントをまとめると:

配列の重複排除の基本形は const unique = [...new Set(array)]
Set は同じ値を何度 add しても 1 回分しか持たない
プリミティブ値(数値・文字列など)の重複排除はこのワンライナーでほぼ解決できる
オブジェクト配列の重複排除では、「どのキーを基準に重複とみなすか」を決めて、そのキーを Set に入れて判定する
従来の「includes でチェックしてから push」より、Set の方が意図が明確でコードも短くなる

まずは、自分のコードの中で

「一度配列を作ったあとに、重複を消したくなっている場所」

を探して、その部分を

const unique = [...new Set(array)];
JavaScript

に置き換えてみてください。

「これだけでよかったんだ」という感覚が、一度でも腹落ちすれば、
Set による重複排除はもうあなたの武器になっています。

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