セット(set)は「重複なし・順序なし」のコレクション
リストやタプルは順序を大事にしますが、set は順序を気にしません。リストでは重複がそのまま残りますが、set では何も言わずに重複が落とされます。次のような場面では、set がちょうどいいデータ構造です。
- とにかく重複を排除したい。
- 要素の有無をすばやく判定したい。
書き方は波かっこ {} を使います。
2つ目の set にあった "red" と "green" の重複が、何事もなかったかのように消えています。これはバグではなく、set の一番の持ち味です。
set の作り方
実際によく使うのは、次の2つのパターンです。
これは誰もが一度はハマるポイントです。{} は空の辞書を作るのであって、空のセットではありません。構文的にどちらかに割り当てるしかなく、辞書に軍配が上がったというわけです。
要素の追加と削除
remove と discard の違いが一番のポイントです。remove は対象の要素が存在していることを前提とし、なければエラーになります。一方 discard は有無を気にしません。要素がないときにエラーにしたいかどうかで使い分けましょう。
高速なメンバーシップ判定
set の真価が発揮されるのがここです。x in some_set は集合のサイズに関係なく定数時間で動きます。対して x in some_list はリストを順に走査するので、リストが大きくなるほど遅くなっていきます。
目安としては、ループの中で if x in some_list のような書き方をしていて、かつリストの要素数が数十件を超えるなら、まずリストを set に変換しておくのが鉄則です。
集合演算(和集合・積集合・差集合)
set の本領はここからです。数学の集合演算そのままの感覚で、演算子を使って集合同士を組み合わせられます。
各演算子にはメソッド版もあります(.union()、.intersection()、.difference()、.symmetric_difference())。演算子のほうが短く書けますが、メソッド版は set 以外の任意のイテラブルも引数に取れるのが強みです。
リストの重複排除
「集合演算」という文脈を離れても、set がいちばんよく使われるのはこの用途でしょう。
一行で重複削除完了です。ただし注意点が一つあって、元の順序は保持されません。重複を排除しつつ元の順序も維持したい場合は、代わりに dict.fromkeys() を使いましょう。
モダンな Python では辞書が挿入順を保持するので、dict.fromkeys に iterable を渡せばその要素をキーとした辞書が作れます。要するに、順序付きの set 代わりとして使えるわけです。
部分集合・上位集合の判定
ある集合が別の集合に含まれているかをチェックするには次のようにします。
こうした操作は、たとえば権限チェック(「このユーザーは必要なロールをすべて持っているか?」)のような場面でよく使われます。
set に入れられるものは?
set の要素になれるのは、ハッシュ可能(hashable) なオブジェクトだけです。これは技術用語ですが、ざっくり言うとこういうことです。
- イミュータブル(不変)なものはハッシュ可能: 数値、文字列、ハッシュ可能な要素からなるタプル、
frozensetなど。 - ミュータブル(可変)なものはダメ: リスト、辞書、他の set は要素にできません。
集合の集合を作りたいときは、frozenset(イミュータブル版のset)を使いましょう。
イテレーションの順序は保証されない
setをループで回しても、要素が出てくる順番は決まっていません。
何度か実行してみると、要素の順序が毎回変わることに気づくはずです。順序が重要な場面では、set は適しません。必要なタイミングで並べ替えるか、素直にリストを使いましょう。
set を使わない方がいい場面
以下のいずれかに当てはまるなら、list や dict の方が向いています。
- 順序が意味を持つ場合
- 重複を保持したい場合
- 各要素に紐づくデータがある場合(要素をキーにした
dictを使いましょう)
次のステップへ
set は「一意性」と「要素の有無」を扱うのが得意なデータ構造です。次に登場する辞書(dict)は、より汎用的な「キーから値を引く」というパターンを担当します。おそらく Python において、リストに次いで最もよく使われるデータ構造と言っていいでしょう。
よくある質問
Pythonのset(集合)って何ですか?
setは「重複を許さない、順序のないコレクション」です。波括弧で書きますが、dictと違ってキーと値のペアは持ちません。例えば colors = {'red', 'green', 'blue'} のように書きます。同じ値を2回入れても無視されて、重複は自動的に消えます。
listではなくsetを使うべきなのはどんなとき?
「重複をなくしたい」「x in collection のような所属チェックを何度もやりたい」場面ではsetが向いています。setは重複を勝手に落としてくれる上、in判定が定数時間(O(1))で済むので、要素数が多くなるほどlistより圧倒的に速くなります。
空のsetはどうやって作るの?
空のsetを作るときは set() を使ってください。{} と書いてしまうと空のdict(辞書)になってしまい、setにはなりません。作ったあとは .add(value) で要素を追加できます。