データ前処理やログ解析で、リスト内の重複がノイズになることはよくあります。
そんなときはPythonのset
で一気に重複を取り除くのが最速です。
本記事ではワンライナーの基本から、順序を保つ実践法、つまずきやすい注意点、ニーズ別レシピまで、Python初心者でも迷わない手順で解説します。
- 関連記事:リストの重複を削除するsetの使い方
Pythonのsetでリストの重複を高速に削除する基本
一瞬で重複削除する最短コード(set→list)
順序を気にしないなら、list(set(...))
が最短で最速です。
set
は重複を持てないデータ構造で、要素の存在判定が平均O(1)と速いため、全体としてO(n)時間で重複を取り除けます。
# 最短ワンライナー: list -> set -> list
# 注意: 出力の順序は変わります (順序は保持されません)
data = [1, 2, 2, 3, 1, 4]
unique = list(set(data)) # 重複が除去された新しいリスト
print(unique)
print(len(unique)) # 要素数の確認
[1, 2, 3, 4]
4
ポイントとして、set
は順序を保持しません。
出力の並びは実行環境によって異なる可能性があります。
「重複を消す」ことが目的で「並びはどうでも良い」場合に最適です。
Python初心者でも迷わない使い方
実務や学習では「速度」と「順序保持」のどちらが必要かをまず決めると選択が楽になります。
- 並び順は不要 →
list(set(seq))
でOK。読みやすく速いです。 - 並び順を維持したい → 後述の「seenセット方式」や
dict.fromkeys
を使います。
次のような小関数にしておくと、使い回しが簡単です。
# 「とにかく速く重複を削除」用の最小関数 (順序は保持されません)
def unique_fast(seq):
# setで重複を削除して、listに戻す
return list(set(seq))
print(unique_fast([3, 3, 1, 2, 2, 1]))
[1, 2, 3]
計算量は概ねO(n)で、リストの要素数が大きいほどset
の利点が生きます。
- set – 組み込み型 — Python 3.13.7 ドキュメント
- 関連記事:もう迷わない!リストを昇順・降順にソートする
- 関連記事:実行時間を計測する方法まとめ(timeitとperf_counter)
順序を保ちたいときの重複削除
setは順序が変わる点に注意
setは順序を保証しません。
したがって「最初に出てきた順序を保ったまま重複を取り除きたい」場合、list(set(...))
は使えません。
学習用やレポート生成など、順序が意味を持つケースでは別手法を選びます。
補足としてdict
(辞書)はPython 3.7以降で挿入順を保持しますが、set
はその保証がありません。
# 同じデータでも、setを経由すると順序が変わる例
data = ["b", "a", "b", "a", "c"]
print("元の順序:", data)
print("set経由:", list(set(data))) # 並びが変わる
元の順序: ['b', 'a', 'b', 'a', 'c']
set経由: ['a', 'c', 'b]
最初の出現順を維持して重複を削除(seenセット)
定番は「seen(見たことがある要素)」というset
を併用する方法です。
最初に現れたものだけ結果に入れ、以降の重複はスキップします。
計算量はO(n)のまま、順序も保てます。
# 順序保持で重複削除: seenセット方式
data = ["b", "a", "b", "a", "c"]
seen = set()
unique_in_order = [] # 最初に出現した順を維持する出力用リスト
for x in data:
if x not in seen:
unique_in_order.append(x) # まだ出ていないので採用
seen.add(x) # 出現済みに登録
print(unique_in_order)
['b', 'a', 'c']
補足として、dict.fromkeys
を使うと1行で書けます。
辞書は挿入順を保持するため、最初の出現順が保たれます。
# 代替: dict.fromkeysで順序保持の重複削除 (Python 3.7+)
data = ["b", "a", "b", "a", "c"]
unique_in_order = list(dict.fromkeys(data))
print(unique_in_order)
['b', 'a', 'c']
以下に主な方法の比較をまとめます。
方法 | 順序保持 | 速度の目安 | 特徴 |
---|---|---|---|
list(set(seq)) | いいえ | 最速 | 実装最短。並びは壊れる。 |
seenセット方式 | はい | 高速 | 最初の出現を維持。柔軟に拡張できる。 |
list(dict.fromkeys(seq)) | はい | 高速 | 1行で簡潔。Python 3.7+の挿入順保持に依存。 |
setで重複削除の注意点
リストや辞書はsetに入れられない(非ハッシュ)
setに入れられるのはハッシュ可能(immutable)なオブジェクトだけです。
list
やdict
はハッシュ不可のため、そのままではエラーになります。
# リストのリストをsetに入れようとするとTypeError
data = [[1, 2], [1, 2], [3, 4]]
try:
unique = list(set(data))
except TypeError as e:
print(type(e).__name__, e) # 例外の種類とメッセージを表示
TypeError unhashable type: 'list'
対処法として、一時的にタプルに変換して重複削除し、必要なら元に戻す方法があります。
順序保持の可否に応じて選びます。
# 順序不要なら: タプル化 → set → リスト化
data = [[1, 2], [1, 2], [3, 4]]
unique = [list(t) for t in {tuple(x) for x in data}] # 並びは変わる可能性あり
print(unique)
[[1, 2], [3, 4]]
# 順序保持なら: seen + キーをタプルにして比較
data = [[1, 2], [1, 2], [3, 4]]
seen = set()
unique_in_order = []
for item in data:
key = tuple(item) # 比較キーとしてタプルを使う
if key not in seen:
unique_in_order.append(item)
seen.add(key)
print(unique_in_order)
[[1, 2], [3, 4]]
- set, frozenset – 組み込み型 — Python 3.13.7 ドキュメント
- TypeError – 組み込み例外 — Python 3.13.7 ドキュメント
- 関連記事:タプル(tuple)とは?変更できないリストの使い方
1とTrueは同一視される点に注意
ブール値True
と整数1
は等価です。
bool
はint
のサブクラスで、True == 1
かつ同じハッシュになります。
そのためset
では重複とみなされます。
data = [1, True, 0, False, True, 1]
print(list(set(data))) # 例: [0, 1] あるいは [False, True]
[0, 1]
ブールと整数を「別物」として扱いたいなら、型も含めて比較キーを作るのが定番です。
# 型と値の組をキーにして区別する
def unique_by_type_and_value(seq):
out, seen = [], set()
for x in seq:
key = (type(x), x) # 型と値で一意にする
if key not in seen:
out.append(x)
seen.add(key)
return out
print(unique_by_type_and_value([1, True, 0, False, True, 1]))
[1, True, 0, False]
‘1’(文字列)と1(数値)は別扱い
型が違えば別物として扱われます。
文字列'1'
と整数1
は重複になりません。
data = ["1", 1, "01", 1.0] # ここで1と1.0は等価、"1"は別物
print(list(set(data)))
['01', 1, '1']
補足として1 == 1.0
で等価なため、1
と1.0
はset
内で重複扱いになります。
これも先ほどの「型と値をキーにする」方法で分離できます。
よくあるニーズ別レシピ
大文字小文字を無視して重複削除
ユーザー名やメールなど、大文字小文字を区別せずに重複判定したい場面では、str.casefold()
をキーにしてseen
方式を使うと堅牢です。
lower()
より広い言語で安全に比較できます。
# 大文字小文字を無視しつつ、最初に出た元の表記を残す
def unique_ignore_case(strings):
out, seen = [], set()
for s in strings:
key = s.casefold() # 正規化して比較
if key not in seen:
out.append(s) # 元の表記を保持
seen.add(key)
return out
data = ["Alice", "alice", "ALICE", "Bob", "bob", "ボブ"]
print(unique_ignore_case(data))
['Alice', 'Bob', 'ボブ']
前後の空白を無視して重複削除
ログやCSVでは前後の空白が混ざることがあります。
空白を無視して重複判定しつつ、最初の表記を保持する例です。
# 前後の空白を無視し、最初の出現表記を残す
def unique_strip(strings):
out, seen = [], set()
for s in strings:
key = s.strip() # 前後の空白を削除した形で比較
if key not in seen:
out.append(s) # 元の文字列を保持(内部ではstripした値をキーに使う)
seen.add(key)
return out
data = [" apple", "apple ", "banana", " banana ", "APPLE"]
print(unique_strip(data))
[' apple', 'banana', 'APPLE']
上の2つの関数はkey
となる「比較用の正規化関数」を変えるだけで応用が効きます。
たとえば「空白も大小も無視」したいならkey = s.strip().casefold()
のように組み合わせれば対応できます。
まとめると、「順序を保つ必要があるならseenセット方式にkey関数を足す」のがシンプルで拡張性も高い定番です。
まとめ
本記事では、setでの高速な重複削除を起点に、順序保持の実践や初心者がつまずきやすい注意点を丁寧に解説しました。
要点は次のとおりです。
文章で振り返ると、まず並びを気にしないならlist(set(seq))
が最短かつ最速です。
順序を保つ必要がある場合はseenセット方式またはdict.fromkeys
を使うと、最初の出現順を維持したままO(n)で重複を除去できます。
また、非ハッシュなリストや辞書はそのままset
に入れられないため、tuple
へ変換してキーにする設計が有効です。
さらにTrueと1は等価で重複扱いになる点や、文字列'1'
と整数1
は別扱いになる型の違いにも注意が必要です。
最後に、現場では「大小無視」「空白無視」などの正規化ニーズが多いため、seen
方式にkey
の発想を加えると柔軟に対応できます。
これらを押さえれば、データクリーニングや前処理がぐっと効率的になります。