Pythonのリストから重複する要素を削除したいとき、「set()を使ったら元の順番が変わってしまった」「辞書を含むリストに対応できない」といった問題に直面することがあります。重複削除の手法は1つではなく、「順序を保持するか否か」と「リストの要素がハッシュ可能か否か」という2軸によって最適な方法が変わります。
間違った手法を選ぶと順番が崩れたり、実行時エラーが発生したりするため、状況に応じた選択が求められます。なお、この記事ではPythonの組み込みlist型(他言語の配列に相当)を対象に解説します。
この記事では、Pythonのリストから重複を削除する代表的な手法を、コピペで動作するサンプルコードとともに紹介していきます。
目次
- Pythonのリスト(list)から重複を削除する方法
- set()で重複を削除する
- dict.fromkeys()で順序を保持して重複を削除する
- forループとsetで順序を保持して重複を削除する
- Pythonのリスト(配列)で重複をチェックする手順
- set()とlen()で重複の有無を判定する
- collections.Counterで重複する要素を特定する
- Pythonで辞書やリストを含むリストの重複を削除する方法
- JSON文字列に変換して重複を削除する
- 特定のキーを基準に辞書リストの重複を削除する
- Pythonのリスト重複削除に関するよくある質問
- set()で重複を削除すると順序が変わるのはなぜですか?
- 2次元リストの重複を削除するにはどうすればよいですか?
- 大量のデータで最も高速な重複削除の方法は何ですか?
Pythonのリスト(list)から重複を削除する方法
Pythonのリストから重複を削除する方法として、以下の3つが代表的です。なお、ここで紹介する3手法はいずれも数値・文字列などハッシュ可能な要素を対象としています。
- set()で重複を削除する
- dict.fromkeys()で順序を保持して重複を削除する
- forループとsetで順序を保持して重複を削除する
set()は最もシンプルですが、順序が保証されません。dict.fromkeys()とforループ+setはいずれも順序を維持したまま重複を除去できます。
用途に応じた選択基準として、「順序不要→set()」「順序保持かつシンプル→dict.fromkeys()」「条件付きフィルタリングも同時に行いたい→forループ+set」と覚えておきましょう。
set()で重複を削除する
set()はPythonの組み込み型で、重複を持たない集合(セット)を表します。リストをset()に渡すと重複が自動的に除去され、さらにlist()で包むことでリストに戻せます。
set()は内部でハッシュテーブルと呼ばれるデータ構造を使っています。ハッシュテーブルとは、各要素をハッシュ値(整数の識別子)に変換して平均O(1)の計算量でアクセスできる仕組みのことです。
ただし、set()はハッシュ値に基づいて要素を管理するため、元のリストの並び順は保証されません。以下のコードで動作を確認してみましょう。
numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
# set()で重複を除去してリストに変換
unique_numbers = list(set(numbers))
print(unique_numbers)
# 出力例: [9, 1, 2, 3, 4, 5, 6] ※出力例のように順序が変わる場合がある
# 文字列リストも同様に使える
fruits = ["apple", "banana", "apple", "cherry", "banana"]
unique_fruits = list(set(fruits))
print(unique_fruits)
# 出力例: ['cherry', 'banana', 'apple'] ※順序は保証されない
元のリストとは異なる順番で要素が出力される場合があります。順番を気にせずユニークな値の集合を取り出すだけでよい場合に適した手法です。
順序を維持する必要があるときは、次のdict.fromkeys()を使う方法を選んでください。
dict.fromkeys()で順序を保持して重複を削除する
dict.fromkeys()は辞書クラスのクラスメソッドで、イテラブルのキーから辞書を生成します。Python 3.7以降、辞書は挿入順序を保持することが言語仕様として保証されており、この特性を利用して順序を維持したまま重複を除去できます。
dict.fromkeys(リスト)に渡すと各要素がキーとして登録され、重複するキーは最初の1件のみが保持されます。なお、各要素が辞書のキーとして使われるため、数値・文字列などハッシュ可能な要素のリストに限定されます。
辞書やリストなど unhashable な要素が含まれる場合はTypeErrorが発生するため、後述のJSON変換アプローチを使用してください。
以下のコードは、dict.fromkeys()で順序を維持しながら重複を削除する例です。
numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
# dict.fromkeys()で順序を保持して重複を除去
unique_numbers = list(dict.fromkeys(numbers))
print(unique_numbers)
# 出力: [3, 1, 4, 5, 9, 2, 6] ※元の順序を保持
# 文字列リストでも使える
fruits = ["apple", "banana", "apple", "cherry", "banana"]
unique_fruits = list(dict.fromkeys(fruits))
print(unique_fruits)
# 出力: ['apple', 'banana', 'cherry'] ※元の順序を保持
元のリストの順番通りに要素が出力される点が、set()との大きな違いです。Python 3.7未満の環境では辞書の挿入順序が言語仕様として保証されていないため、順序を維持する目的でのこの手法は信頼できません(CPython 3.6では実装上偶然保持されますが、言語仕様ではありません)。
現在のPythonプロジェクトでは3.7以降が一般的なため、順序を保ちながら重複削除する場面での標準的な選択肢として広く使われています。
the insertion-order preservation nature of dict objects has been declared to be an official part of the Python language spec.
出典:Python公式ドキュメント - What's New In Python 3.7
forループとsetで順序を保持して重複を削除する
forループとsetを組み合わせる手法では、「既に見た要素」を追跡するためのsetを用意し、set未登録の要素のみを新しいリストに追加していきます。setへの追加は平均O(1)で完了するため(要素がハッシュ可能な場合)、全体の処理は平均O(n)の計算量で実行されます。
dict.fromkeys()と同様に元の順序を保持でき、さらに条件付きフィルタリング(例: 特定の値を除外しながら重複削除)を同時に行いたい場合に柔軟に応用できる点が優位です。
以下のコードはforループ版(推奨)と、参考としてリスト内包表記版を示しています。内包表記版は副作用(setへの追加)を条件式に組み込むトリッキーな記法であり、チーム開発では可読性が低下するため通常は推奨されません。
numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
# forループで明示的に順序を保ちながら重複を削除する(推奨)
seen = set()
unique_numbers = []
for x in numbers:
if x not in seen:
seen.add(x)
unique_numbers.append(x)
print(unique_numbers)
# 出力: [3, 1, 4, 5, 9, 2, 6] ※元の順序を保持
# 【参考】内包表記版(短く書けるが副作用を伴い可読性が落ちる)
seen2 = set()
# seen.add()はNoneを返すため、登録と条件判定を1行で行うテクニック
unique_numbers2 = [x for x in numbers if x not in seen2 and not seen2.add(x)]
print(unique_numbers2)
# 出力: [3, 1, 4, 5, 9, 2, 6] ※同じ結果
forループ版は動作を明示的に記述しており、コードレビューや保守の場面で読みやすい実装です。条件付きフィルタリングを加える場合も、forループ版であればif文を追記するだけで対応できます。
Pythonのリスト(配列)で重複をチェックする手順
Pythonのリストに重複する要素が含まれているかを調べる操作は、「重複の有無を判定する」と「どの要素が重複しているかを特定する」の2つに分けられます。用途に応じて適切な手法を選ぶことによって、コードの可読性と処理効率を両立させられます。
以下の2つの手順を順番に解説します。
- set()とlen()で重複の有無を判定する
- collections.Counterで重複する要素を特定する
1つ目は「重複が存在するかどうか」を真偽値で返すシンプルな判定で、2つ目は「どの要素が何回出現しているか」を詳しく調べる手法です。それぞれの特徴と使いどころを理解しておくと、実装の選択肢が広がります。
set()とlen()で重複の有無を判定する
set()への変換は平均O(n)のコストがかかります(nはリストの要素数)。変換前後の要素数をlen()で比較することによって、重複の有無を線形時間で判定できます。
「重複削除する必要があるか」を事前に確認したい場合に活用できます。
以下のコードは、リストに重複が含まれているかどうかをTrueまたはFalseで返す関数の実装例です。
def has_duplicates(lst):
return len(lst) != len(set(lst))
# 使用例
fruits = ["apple", "banana", "apple", "cherry"]
numbers = [1, 2, 3, 4, 5]
print(has_duplicates(fruits)) # True
print(has_duplicates(numbers)) # False
len(lst)とlen(set(lst))の値が一致しない場合、重複が存在することを意味します。この手法は標準ライブラリのみで実装でき、コードが簡潔にまとまる点が優れています。
ただし、set()への変換はハッシュ可能な要素(数値や文字列、中身もハッシュ可能なタプルなど)にのみ対応しています。辞書やリストなどのミュータブルなオブジェクトはハッシュ不可のため、このアプローチではTypeErrorが発生します。
辞書やリストを含む場合の重複削除については「Pythonで辞書やリストを含むリストの重複を削除する方法」を参照してください。
collections.Counterで重複する要素を特定する
collections.Counterは、Pythonの標準ライブラリcollectionsモジュールに含まれるdictのサブクラスで、ハッシュ可能な要素の出現回数を集計します。
単に重複の有無を調べるだけではなく、「どの要素が重複しているか」「何回重複しているか」まで一度に把握できる点が、set()との大きな違いです。なお、辞書やリストなどハッシュ不可能な要素はCounterの引数に渡せないため注意してください。
以下のコードは、Counterを使って重複する要素の一覧を取得する実装例です。most_common()メソッドを使うことによって、出現回数が多い順に要素を並べ替えて取得できます。
from collections import Counter
fruits = ["apple", "banana", "apple", "cherry", "banana", "apple"]
# 各要素の出現回数を集計
counter = Counter(fruits)
print(counter)
# Counter({'apple': 3, 'banana': 2, 'cherry': 1})
# 出現回数が2回以上の要素のみを抽出
duplicates = [item for item, count in counter.items() if count >= 2]
print(duplicates)
# ['apple', 'banana']
# 出現回数が多い順に上位2件を取得
most_common = counter.most_common(2)
print(most_common)
# [('apple', 3), ('banana', 2)]
Counter(fruits)を実行すると、各要素をキー、出現回数を値としたCounterオブジェクト(dictのサブクラス)が生成されます。この結果に対してitems()でキーと値のペアを取り出し、リスト内包表記で出現回数が2以上の要素を絞り込むことによって、重複要素の一覧を取得できます。
most_common(n)は出現回数が多い上位n件を返すメソッドで、頻出ワードの抽出やログ解析など、重複の多寡を分析したい場面で活用できます。単純な有無の判定にはset()を、要素ごとの出現回数や頻度順の取得が必要な場面にはCounterを選択するとよいでしょう。
Pythonで辞書やリストを含むリストの重複を削除する方法
辞書(dict)やリスト(list)などのミュータブル(変更可能)なオブジェクトを要素として含むリストでは、set()による重複削除が使えません。ハッシュ可能(hashable)とは、「生存期間中変わらないハッシュ値を持ち、他のオブジェクトと比較可能で、等しいオブジェクトは同じハッシュ値を持つ」ことを意味します(Python公式Glossaryの定義)。
辞書やリストはこの条件を満たさないため、set()に渡そうとするとTypeError: unhashable typeエラーが発生します。
ここでは、ハッシュ不可能な要素を含むリストの重複を削除するための以下の2つのアプローチを紹介します。
- JSON文字列に変換して重複を削除する
- 特定のキーを基準に辞書リストの重複を削除する
1つ目はJSON互換のデータ型全般に対応できる汎用的な手法で、2つ目は特定キーで重複を判定したい場合に適した手法です。それぞれの特性と計算量も合わせて確認しておきましょう。
それでは各手法について、詳しく解説していきます。
JSON文字列に変換して重複を削除する
シリアライズとは、オブジェクトをバイト列や文字列などの保存・転送可能な形式に変換する処理のことです。json.dumps()を使って辞書やリストをJSON形式の文字列(文字列はハッシュ可能)にシリアライズし、set()で重複を除去してから元のデータを保持するパターンです。
この手法はJSONで表現できるデータ型(辞書・リスト・文字列・数値・ブール値・None)に限定される点に注意してください。
キーの順序が異なる辞書(例: {"a": 1, "b": 2}と{"b": 2, "a": 1})を同一内容として扱いたい場合は、sort_keys=Trueを指定してください。これによりキーがソートされた順で並び替えられ、同一内容の辞書が同じ文字列として扱われます。
なお、キー順序の違いを区別したい場合はsort_keys=Trueの指定は不要です。
import json
# 辞書を含むリスト(重複あり)
data = [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"},
{"id": 1, "name": "Alice"}, # 重複
{"name": "Alice", "id": 1}, # キー順序違いだが同一内容
]
# json.dumps()でJSON文字列化 → setで重複除去 → 元データを順序保持で収集
seen = set()
result = []
for item in data:
# sort_keys=Trueでキー順を統一してからシリアライズ
serialized = json.dumps(item, sort_keys=True)
if serialized not in seen:
seen.add(serialized)
result.append(item)
print(result)
# 出力: [{'id': 1, 'name': 'Alice'}, {'id': 2, 'name': 'Bob'}]
# ネストされたリストへの応用
nested = [[1, 2], [3, 4], [1, 2], [5, 6]]
seen2 = set()
result2 = []
for item in nested:
serialized = json.dumps(item, sort_keys=True)
if serialized not in seen2:
seen2.add(serialized)
result2.append(item)
print(result2)
# 出力: [[1, 2], [3, 4], [5, 6]]
上記コードではseenというセットにシリアライズ済み文字列を蓄積し、初めて登場した要素のみresultに追加しています。これにより挿入順序も保持されます。
この手法の計算量はO(n×k)です(nは要素数、kはJSON文字列長であり各辞書のフィールド数・値の長さに依存する変数)。setの検索はO(1)ですが、各要素をシリアライズする処理にO(k)かかります。
float('nan')やfloat('inf')については、Pythonのjson.dumps()はデフォルト(allow_nan=True)でNaN・Infinity・-Infinityを非標準な文字列としてそのまま出力します(RFC 8259準拠ではありません)。
そのため、Python以外の言語や厳密なJSONパーサーに渡す場合はエラーになる可能性があります。
nan/infを厳密に弾きたい場合はallow_nan=Falseを指定するとValueErrorが発生します。NoneはJSONのnullとして正常に変換されるため問題ありません。
If sort_keys is true (default: False), then the output of dictionaries will be sorted by key.
出典:Python公式ドキュメント - json.dumps
| 項目 | 内容 |
|---|---|
| 計算量 | O(n×k) ※kはJSON文字列長(フィールド数・値の長さに依存) |
| 順序保持 | 保持する(挿入順) |
| 対象データ型 | JSON互換の型(辞書・リスト・文字列・数値・bool・None)に限定 |
| 注意点 | キー順序を無視したい場合はsort_keys=True、nan/infはRFC非準拠(厳密に弾くにはallow_nan=False)、datetime・関数・集合は非対応 |
JSON変換を利用したこのパターンは、辞書とリストの両方に対応できる汎用性が特長です。ただし、関数やdatetimeオブジェクト、集合、循環参照を含む場合は使えないため、データの型を事前に確認しておきましょう。
特定のキーを基準に辞書リストの重複を削除する
辞書のリストで「特定のキーの値が同じ要素を重複とみなして削除する」場合には、全フィールドの一致ではなく特定キーのみを基準にする手法が実用的です。例えばAPIレスポンスで"id"フィールドが一致する辞書を重複と判定するケースで活用できます。
なお、特定キーの値がハッシュ可能な型(文字列・整数など)である場合に使用できます。キー値に辞書やリストが含まれる場合はJSON変換アプローチを使用してください。
特定のキーの値をseenセットに追加するシンプルな実装で対処できます。参考として、辞書全体をタプルに変換してハッシュ化するアプローチ(全フィールドの完全一致で重複を判定する手法)も後述します。
このタプル変換は値もハッシュ可能な浅い辞書(値に辞書やリストを含まない場合)にのみ使用できる点に注意してください。
# 特定のキー("id")を基準に重複を削除する
data = [
{"id": 1, "name": "Alice", "score": 90},
{"id": 2, "name": "Bob", "score": 80},
{"id": 1, "name": "Alice", "score": 95}, # id=1の重複(scoreが違っても重複扱い)
{"id": 3, "name": "Carol", "score": 70},
]
seen_ids = set()
result = []
for item in data:
key = item["id"]
if key not in seen_ids:
seen_ids.add(key)
result.append(item)
print(result)
# 出力:
# [{'id': 1, 'name': 'Alice', 'score': 90},
# {'id': 2, 'name': 'Bob', 'score': 80},
# {'id': 3, 'name': 'Carol', 'score': 70}]
# 【参考】辞書全体をタプルに変換してハッシュ化するアプローチ(全フィールド完全一致の判定用)
# 値がハッシュ可能な浅い辞書に限定して使用可能
# sorted()は挿入順が異なる同一内容の辞書を同一視するために必要
data2 = [
{"name": "Alice", "score": 90},
{"name": "Bob", "score": 80},
{"name": "Alice", "score": 90}, # 完全に同一の重複
]
seen_tuples = set()
result2 = []
for item in data2:
# items()をソートしてタプル化(ハッシュ可能になる)
key = tuple(sorted(item.items()))
if key not in seen_tuples:
seen_tuples.add(key)
result2.append(item)
print(result2)
# 出力: [{'name': 'Alice', 'score': 90}, {'name': 'Bob', 'score': 80}]
1つ目の実装では"id"のみを基準にするため、同じidを持つ別スコアの辞書は最初に出現したものが採用されます。2つ目のタプル変換アプローチでは辞書の全フィールドが完全一致するものを重複とみなします。
sorted()を使うことで挿入順が異なる同一内容の辞書も同一視できますが、混在キーを含む場合はソートでTypeErrorになる可能性があります。値にリストや辞書など unhashable なオブジェクトが含まれる場合もエラーになるため、その場合はJSON変換アプローチを使ってください。
| 手法 | 計算量 | 用途 |
|---|---|---|
| 特定キーで判定 | O(n) | IDなど識別子が明確な場合(キー値がハッシュ可能な型に限定) |
| タプル変換で全フィールド判定 | O(n×m log m) ※mはキー数(sorted()のコスト) | 全フィールド完全一致で重複とする場合(値がハッシュ可能な浅い辞書に限定) |
| JSON文字列変換 | O(n×k) | ネスト構造を含む汎用的な場合 |
ループ内でin listを使って重複チェックする実装も見られますが、リスト全体を毎回走査するためO(n²)の計算量となり、要素数が増えるにつれて処理速度が急激に低下します。
上記のようにsetを補助的に使って重複を管理することによって、多くの場合はO(n)またはO(n×m log m)での処理が実現可能です。データの特性に応じて適切な手法を選択してください。
Pythonのリスト重複削除に関するよくある質問
set()で重複を削除すると順序が変わるのはなぜですか?
set()は内部でハッシュテーブルという仕組みを使って要素を管理しており、各要素はハッシュ値に基づいたメモリ位置に格納されます。この構造は重複の有無を高速に判定するために最適化されており、挿入順序を保持する設計ではありません。
そのため、set()に変換した時点で要素の並び順は元のリストと異なる場合があります。順序を保ちながら重複を削除したい場合は、要素がハッシュ可能であればdict.fromkeys()を使うのが推奨です。
2次元リストの重複を削除するにはどうすればよいですか?
リストはハッシュ化できないため、set()に直接渡してもエラーになります。2次元リストの重複を削除するには、内側のリストをタプルに変換するか、JSON文字列にシリアライズしてハッシュ可能な形式に変換してから処理します。
詳しい実装方法は「Pythonで辞書やリストを含むリストの重複を削除する方法」で解説しているので、そちらを参照してください。
大量のデータで最も高速な重複削除の方法は何ですか?
順序を問わない場合、set()への変換が平均的に高速です。ハッシュテーブルによる平均O(1)の検索を活かし、リストのサイズに比例した線形時間で処理が完了します。
数百万件規模でPandasやNumPyのデータ構造をすでに利用している場合は、drop_duplicates()やnumpy.unique()の活用も選択肢です。ただし、リストからの変換コストがあるため、小〜中規模データではset()の方が高速なケースが多いです。
※上記コンテンツの内容やソースコードはAIで確認・デバッグしておりますが、間違いやエラー、脆弱性などがある場合は、コメントよりご報告いただけますと幸いです。
ITやプログラミングに関するコラム
PythonをWebで実行する方法
共通テスト「情報Ⅰ」2年目で変わる、日本の教育と学び方
gitでブランチ(branch)を切り替える方法
git cloneでブランチを指定する方法
64GBのメモリが必要な人・不要な人の特徴
PCを再起動するコマンド一覧
CapsLock以外で大文字になる原因【Windows編】
パソコンで大文字になるのを解除する方法
面白いAIの活用事例を業界別に紹介
Gitでcommit(コミット)を取り消す方法
ITやプログラミングに関するニュース
サイボウズがkintone AIを正式提供、β版から約1年を経てクレジット制を導入
ロゼッタのラクヤクAIがCSRドラフト作成期間を90%以上短縮、従来4週間を約2日に
AI CROSSが不動産業界向け生成AI伴走支援を開始、アスコットの業務AI実装を実践サポート
日本情報クリエイトが「オーナー提案AIロボⅡ」売買査定を刷新、月1万円からW査定が回数無制限に
Wur株式会社がAI新規事業診断サービス「MVP事業診断レポート」をリリース、12の質問で事業構想を約10分で分析
バトンズがM&A専門家向け「AI概要書」β版を提供開始、企業概要書のドラフトを最速3分で自動生成
SCSKが観光DXサービス「Connexia」を開発、首里城公園でNFT活用の周遊促進が始動
Verdent AI発表、エンジニア不要でソフトウェアを構築する「AIエンジニアリングチーム」が登場
ゼネラルBREXAテクノロジーが外食・小売向けAIサービス「aimana」を開発、店長の意思決定をデータで支援
田中組がKencopa工程AIエージェント製品版を先行利用開始、建設現場の工程管理属人化を解消へ
