set型について

個人的にはPythonでなにかとlistを利用することが多かったのですが、set型について知って、改めて適切な型を利用するのが大切だなと感じました。

set型は以下のような特徴を持ちます。

  • 順序がない
  • 挿入順序、要素位置は保持しない
  • 重複を削除できる
  • 和集合や積集合といった計算ができる
  • ハッシュ可能なオブジェクトしか集合の要素にできない(id:XaroCydeykn さんから教えてもらいました)

https://docs.python.org/ja/3/library/stdtypes.html#set-types-set-frozenset

より簡易的にまとめました。

順序を意識しなくて良いのなら、listで受け取った値をそのまま使って処理をするのではなく、setを利用することでより効果的な処理を実現できる可能性があります。

メリット

重複を削除できることによって、listで受け取った値の重複チェックみたいな処理を書く必要がありません。

例えば、以下のような記述をすることでset型を利用する事ができます。

# 空のsetを作成する
foo = set()

# 空ではないsetを作成する
bar = {1, 2, 2, 3, 3, 3}
bar  # {1, 2, 3} が出力される

# listを元にsetを作成する
baz = [1, 2, 2, 3, 3, 3]
baz = set(baz)
baz # {1, 2, 3}

bar, bazの変数の例を見ると、重複された値が削除された事がわかります。

また、エキスパートPythonプログラミング 改訂4版曰く以下のようなメリットもあるようです。

set型を使えば、dictと同じようにハッシュを利用して探索を行うので高速になります。

エキスパートPythonプログラミング 改訂4版

著 Micha l Jaworski 著 Tarek Ziade

訳 新井 正貴 訳 稲田 直哉 訳 渋川 よしき 訳 清水川 貴之 訳 福田 隼也

P537 より引用

デメリット

順序がないため、例えばPython2系の時のdictのような挙動になります。 そのため、順序を保持する必要がある処理ではsetをそのまま利用する事ができません。

※順序を保持してlistを作成する方法もエキスパートPythonプログラミング 改訂4版には訳註が記載されていましたが内容の丸写しになりそうなので割愛します。

内容を保持する必要があるなら、別の方法を取ることが求められます。

ハッシュ可能なオブジェクトしか集合の要素にできない

id:XaroCydeykn さんから教えてもらった内容について追記。

例えば、以下のようなコードを書くとlistはハッシュ可能なオブジェクトではないため、失敗します。

foo = [1, 2, 3]
foo_set = set()
foo_set.add(foo)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

公式リファレンスにも以下のように記述があります。

iterable から要素を取り込んだ、新しい set もしくは frozenset オブジェクトを返します。 集合の要素は ハッシュ可能 なものでなくてはなりません。

https://docs.python.org/ja/3/library/stdtypes.html#set より引用

set() で生成するときはiterableを元にオブジェクトを返すため生成できますが、addで追加しようとすると失敗します。

そのため、listのまま追加しようとすると以下のようにタプルへ変換してやる必要があるとのことでした。

foo = [1, 2, 3]
foo_tuple = tuple(foo)
foo_set.add(foo_tuple)
foo_set  # {(1, 2, 3)} が出力される

コメントをもらって、最初に自分が書いたコードがなぜ動いているか一瞬わからなくなりましたが、これでもうちょっとちゃんと説明できそうです。

また、set(集合)の説明は以下の書籍にも記載があるので、setとは?となった方がいたらオススメです。

Python Distilled ―プログラミング言語Pythonのエッセンス David M. Beazley 著、鈴木 駿 訳

https://www.oreilly.co.jp/books/9784814400461/