エンジニアを目指す初学者に向けて、わかりやすく解説したブログです。

新人エンジニアへ「それ、setでよくない?」

以下と似たようなコードを書いたことがないだろうか?

const userIds: string[] = [];

// APIのレスポンスをforループする
responses.forEach(response => {
  response.users.forEach(user => {
    // リストにユーザーIDが含まれていなければ、新しく追加する(重複排除のため)
    if (!userIds.includes(user.id)) {
      userIds.push(user.id);
    }
  });
});

今回は、このようなコードを書く前に
「そもそもこれはlistで扱うべきデータなのか?」を一度立ち止まって考えよう、という話をする。

list,setの違い

およそどのプログラミング言語にも、この2種類のデータ構造は標準で搭載されている。

今回はTypeScriptベースで紹介するが、基本的な考え方は他のプログラミング言語でも同じだ。

観点 list set
目的 データの並びを扱う データの集合を扱う
重複 重複OK 重複NG
順序 保証される 保証されない※

listとsetでは、どんなデータを扱うかという目的がはっきりと異なる

データの並び順が重要であるか、重複が許されるかどうかを基準に使い分けるのが望ましい。

※JSでは挿入順が存在するが、意味論として順序に期待すべきではない

なぜ使い分ける必要があるのか?

「listで書けるならlistでいいじゃん」と思うかもしれないが、
使い分けることには明確なメリットが存在する。

①記述するロジックが減る

冒頭のサンプルコードで説明したように、「重複を排除したい」という実装はよく遭遇する。

その場合、setで書くほうが圧倒的にシンプルに書くことができる。

setを使っている限り、「重複が排除されていること」のようなテストコードを書く必要もない。

■listで書いたコード

const userIds: string[] = [];

responses.forEach(response => {
  response.users.forEach(user => {
    if (!userIds.includes(user.id)) {
      userIds.push(user.id);
    }
  });
});

■setで書いた場合

要素の追加をするときに「既に含まれているか」という確認をわざわざ行う必要がない。

const userIds = new Set<string>();

responses.forEach(response => {
  response.users.forEach(user => {
    userIds.add(user.id); // 重複していた場合はsetが自動的に排除してくれる
  });
});

②データの意図が明確になる

例えば、記事のタグ一覧データがあったとき、
listを使っているかsetを使っているかで操作時に考えることが変わってくる。

■listで書かれている場合

type Article = {
  id: string;
  tags: string[];
};

もしこのように書かれていたら、以下のことを考えながら実装しなくてはいけない

  • タグ一覧は何らかの意味でソートされているのか?もしそうだとしたら順序は維持して処理しなきゃいけないな
  • もし重複したタグがあったらどうすればいいんだ?そのまま画面に表示していいのか?重複は排除したほうがいいのか?

■setで書かれている場合

type Article = {
  id: string;
  tags: Set<string>;
};

setとして書かれていた場合は、実装時に上記のような悩みを抱えることはない。

  • タグを表示するときは、順序は気にしなくていいんだな
  • 重複は絶対ありえないから、重複チェックとかは必要なさそうだな

型を見ただけで「絶対重複はありえません」「順序は気にしなくていいです」と理解できることは
実装時の認知負荷を大きく下げることに繋がる。

③パフォーマンスが良い

言語によって多少差分はあると思うが、基本的には以下のような傾向にある。

(少量のデータであれば気にするほどでもないが)

  • 要素の追加:listよりsetのほうが遅い(setは重複チェックが必要なため)
  • 要素の削除:listよりsetのほうが速い(listは要素のシフトなどが必要なため)
  • 要素の検索:listよりsetのほうが速い(listは先頭から順に比較する必要があるが、setはハッシュ探索可能なため)

「含まれているか」「既に存在するか」をよく確認する場合は、
listよりsetのほうが意味的にも、パフォーマンス的にも適切である。

まとめ

  • setを使うことで「重複がない」「順序が重要ではない」という意図を適切に表現できる
  • 特に重複チェックはsetを使うほうが簡単である
  • 日本語で「一覧」と言われたらついlistを使いがちだが、setのほうが適切ではないか?と考えてみる
    • 例:「ブラックリスト」などはリストと言いつつset管理のほうが適切