タイトル: セットの作成
SEOタイトル: Python set 入門 — 作成方法、frozenset、内包表記、リスト重複削除
| この記事の要点 |
|
セット (set) とは
同じ値を 1 つしか保持しない、順序を持たないコレクションです。数学の「集合」に近く、合併 / 共通部分 / 差集合といった集合演算をそのまま行えます。要素アクセス・追加・削除はすべて平均 O(1)。
作成方法
# 波括弧リテラル
s = {1, 2, 3}
print(s) # {1, 2, 3}
# 重複は自動で 1 つに
s = {1, 2, 2, 3, 3, 3}
print(s) # {1, 2, 3}
# set() コンストラクタ + イテラブル
s = set([1, 2, 3])
s = set((1, 2, 3))
s = set("hello") # {'h', 'e', 'l', 'o'}
# 空セット
empty = set() # ← OK
# empty = {} # ❌ これは空 dict
print(type(empty)) #
print(type({})) #
初心者がよくつまずくのが {}。空の波括弧は dict です。空 set を作るには必ず set()。
frozenset — 不変版
fs = frozenset([1, 2, 3])
print(fs) # frozenset({1, 2, 3})
# 変更不可
# fs.add(4) # AttributeError
# ハッシュ可能なので dict のキーや set の要素になれる
d = {frozenset({1, 2}): "two values"}
nested = {frozenset({1}), frozenset({2})}
セット内包表記
# 1〜10 の偶数
evens = {n for n in range(1, 11) if n % 2 == 0}
# {2, 4, 6, 8, 10}
# 文字列から記号を除いたユニーク文字
text = "Hello, World!"
chars = {c.lower() for c in text if c.isalpha()}
# {'h', 'e', 'l', 'o', 'w', 'r', 'd'}
# 複数イテラブルから
pairs = {(a, b) for a in [1, 2] for b in ['x', 'y']}
# {(1, 'x'), (1, 'y'), (2, 'x'), (2, 'y')}
典型用途 — リストの重複削除
nums = [1, 2, 2, 3, 4, 4, 4, 5]
# 重複なしリスト (順序は保持されない)
unique = list(set(nums))
# 例: [1, 2, 3, 4, 5]
# 順序を保ったまま重複削除 (Python 3.7+ で dict が挿入順を保証)
unique = list(dict.fromkeys(nums))
# [1, 2, 2, 3, 4, 4, 4, 5] → [1, 2, 3, 4, 5]
要素の追加・削除
s = {1, 2, 3}
# 単一要素を追加
s.add(4) # {1, 2, 3, 4}
s.add(2) # 既にあれば無視 → {1, 2, 3, 4}
# 複数要素を一気に追加 (集合の合併で破壊的更新)
s.update([5, 6, 7]) # {1, 2, 3, 4, 5, 6, 7}
s |= {8, 9} # {1, 2, ..., 9}
# 削除
s.remove(9) # 無いと KeyError
s.discard(99) # 無くてもエラーにならない
removed = s.pop() # 任意の 1 要素を取り出して削除
s.clear() # 全削除 → set()
集合演算
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
# 合併 (union)
a | b # {1, 2, 3, 4, 5, 6}
a.union(b)
# 共通部分 (intersection)
a & b # {3, 4}
a.intersection(b)
# 差集合 (difference)
a - b # {1, 2}
a.difference(b)
# 対称差 (symmetric difference) - どちらか一方のみに含まれる
a ^ b # {1, 2, 5, 6}
a.symmetric_difference(b)
# 部分集合 / 上位集合
{1, 2}.issubset({1, 2, 3}) # True
{1, 2, 3}.issuperset({1, 2}) # True
{1}.isdisjoint({2, 3}) # True — 共通要素なし
使い分け — list / tuple / set / dict
| 型 | 順序 | 重複 | 変更 | 典型用途 |
|---|---|---|---|---|
| list | あり (挿入順) | 可 | 可 | 順序が大事、インデックスでアクセス |
| tuple | あり | 可 | 不可 | 固定長レコード、dict キー |
| set | なし | 不可 | 可 | 重複排除、集合演算、存在判定 |
| frozenset | なし | 不可 | 不可 | dict キー、set の要素 |
| dict | あり (挿入順, 3.7+) | キーは不可 | 可 | キー検索、レコード |
パフォーマンス — なぜ set は速い?
内部はハッシュテーブルで、要素検索 (x in s) は平均 O(1) です。リストでの x in lst は線形検索の O(n) なので、大量の存在判定がある場面では set にしてから検索するのが定石。
# 100 万件から特定要素の存在判定
big = list(range(1_000_000))
target = 999_999
# list 内検索 — 遅い
target in big # 約 0.02 秒
# set にしてから検索 — 高速
big_set = set(big)
target in big_set # 約 0.00001 秒 (1000 倍以上速い)
制約 — 要素はハッシュ可能のみ
# ✅ ハッシュ可能 (immutable)
s = {1, "hello", (1, 2), frozenset({3, 4})}
# ❌ ハッシュ不可
# s = {[1, 2]} # TypeError: unhashable type: 'list'
# s = {{"a": 1}} # TypeError: unhashable type: 'dict'
# s = {{1, 2}} # TypeError: unhashable type: 'set'
# リストを set に入れたいなら tuple に変換
s = {tuple([1, 2]), tuple([3, 4])}
FAQ
Q: {} が dict なのに、要素を入れた {1, 2} が set なのはなぜ?
A: 歴史的経緯で {} は元から dict のリテラル。要素がある場合は構文的に区別できるので set として解釈されます。空 set は必ず set()。
Q: set の順序は保証されない?
A: 言語仕様として保証されません。Python 3.6 以降の CPython 実装では挿入順に似た挙動を示すことが多いですが、依存してはいけません。
Q: 重複削除しつつ元の順序を保ちたい
A: Python 3.7+ なら list(dict.fromkeys(seq)) が定番。OrderedDict 不要で読みやすく速い書き方です。