閉じる

Pythonのsetでリストの重複を一瞬で削除する方法と注意点

データ前処理やログ解析で、リスト内の重複がノイズになることはよくあります。

そんなときはPythonのsetで一気に重複を取り除くのが最速です。

本記事ではワンライナーの基本から、順序を保つ実践法、つまずきやすい注意点、ニーズ別レシピまで、Python初心者でも迷わない手順で解説します。

Pythonのsetでリストの重複を高速に削除する基本

一瞬で重複削除する最短コード(set→list)

順序を気にしないなら、list(set(...))が最短で最速です。

setは重複を持てないデータ構造で、要素の存在判定が平均O(1)と速いため、全体としてO(n)時間で重複を取り除けます。

Python
# 最短ワンライナー: 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を使います。

次のような小関数にしておくと、使い回しが簡単です。

Python
# 「とにかく速く重複を削除」用の最小関数 (順序は保持されません)
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は順序が変わる点に注意

setは順序を保証しません

したがって「最初に出てきた順序を保ったまま重複を取り除きたい」場合、list(set(...))は使えません。

学習用やレポート生成など、順序が意味を持つケースでは別手法を選びます。

補足としてdict(辞書)はPython 3.7以降で挿入順を保持しますが、setはその保証がありません。

Python
# 同じデータでも、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)のまま、順序も保てます。

Python
# 順序保持で重複削除: 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行で書けます。

辞書は挿入順を保持するため、最初の出現順が保たれます。

Python
# 代替: 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)なオブジェクトだけです。

listdictはハッシュ不可のため、そのままではエラーになります。

Python
# リストのリストを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'

対処法として、一時的にタプルに変換して重複削除し、必要なら元に戻す方法があります。

順序保持の可否に応じて選びます。

Python
# 順序不要なら: タプル化 → 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]]
Python
# 順序保持なら: 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]]

1とTrueは同一視される点に注意

ブール値Trueと整数1は等価です。

boolintのサブクラスで、True == 1かつ同じハッシュになります。

そのためsetでは重複とみなされます。

Python
data = [1, True, 0, False, True, 1]
print(list(set(data)))  # 例: [0, 1] あるいは [False, True]
実行結果
[0, 1]

ブールと整数を「別物」として扱いたいなら、型も含めて比較キーを作るのが定番です。

Python
# 型と値の組をキーにして区別する
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は重複になりません。

Python
data = ["1", 1, "01", 1.0]  # ここで1と1.0は等価、"1"は別物
print(list(set(data)))
実行結果
['01', 1, '1']

補足として1 == 1.0で等価なため、11.0set内で重複扱いになります。

これも先ほどの「型と値をキーにする」方法で分離できます。

よくあるニーズ別レシピ

大文字小文字を無視して重複削除

ユーザー名やメールなど、大文字小文字を区別せずに重複判定したい場面では、str.casefold()をキーにしてseen方式を使うと堅牢です。

lower()より広い言語で安全に比較できます。

Python
# 大文字小文字を無視しつつ、最初に出た元の表記を残す
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では前後の空白が混ざることがあります。

空白を無視して重複判定しつつ、最初の表記を保持する例です。

Python
# 前後の空白を無視し、最初の出現表記を残す
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の発想を加えると柔軟に対応できます。

これらを押さえれば、データクリーニングや前処理がぐっと効率的になります。

Python 実践TIPS - コーディング効率化・Pythonic
この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!