以下と似たようなコードを書いたことがないだろうか?
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管理のほうが適切