Pythonのリストで重複するdictを削除する


153

辞書のリストがあり、同じキーと値のペアを持つ辞書を削除したいと思います。

このリストの場合: [{'a': 123}, {'b': 123}, {'a': 123}]

返品したいです: [{'a': 123}, {'b': 123}]

もう一つの例:

このリストの場合: [{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}, {'a': 123, 'b': 1234}]

返品したいです: [{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}]


解決しようとしている実際の問題について詳しく教えてください。これは奇妙な問題のようです。
gfortune 2012

私は辞書のいくつかのリストを組み合わせていますが、重複があります。したがって、これらの重複を削除する必要があります。
ブレンデン2012

私はstackoverflow.com/questions/480214/…で解決策を見つけましたset()
Sebastian Wagner

回答:


242

これを試して:

[dict(t) for t in {tuple(d.items()) for d in l}]

戦略は、辞書のリストを、タプルに辞書の項目が含まれるタプルのリストに変換することです。タプルをハッシュできるため、set(ここでは集合内包を使用して、古いpythonの代替はを使用して)を使用して重複を削除set(tuple(d.items()) for d in l)し、その後、を使用してタプルから辞書を再作成できますdict

どこ:

  • l 元のリストです
  • d リスト内の辞書の1つです
  • t 辞書から作成されたタプルの1つ

編集:順序を維持したい場合、上記のワンライナーは機能しないため機能しsetません。ただし、数行のコードで、次のことも実行できます。

l = [{'a': 123, 'b': 1234},
        {'a': 3222, 'b': 1234},
        {'a': 123, 'b': 1234}]

seen = set()
new_l = []
for d in l:
    t = tuple(d.items())
    if t not in seen:
        seen.add(t)
        new_l.append(d)

print new_l

出力例:

[{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}]

注:@alexisで指摘されているように、同じキーと値を持つ2つの辞書が同じタプルにならない場合があります。これは、キーの追加/削除の履歴が異なる場合に発生する可能性があります。それがあなたの問題に当てはまる場合は、d.items()彼が示唆するように並べ替えを検討してください。


35
素晴らしい解決策ですが、バグd.items()があります。特定の順序で要素を返すことが保証されていません。あなたは行う必要がありtuple(sorted(d.items()))ますが、同じキーと値のペアごとに異なるタプルを取得しないことを確認します。
アレクシス

@alexis私はいくつかのテストを行いましたが、あなたは本当に正しいです。間に多くのキーが追加され、後で削除された場合は、そうなる可能性があります。コメントありがとうございます。
jcollado

涼しい。会話全体を読まない可能性のある将来の読者のために、回答に修正を加えました。
アレクシス

2
注:json私が行ったように、モジュールから
ディクテーションの

2
これはこの場合有効な解決策ですが、辞書がネストされている場合は機能しません
Lorenzo Belli

51

リスト内包表記に基づく別のワンライナー:

>>> d = [{'a': 123}, {'b': 123}, {'a': 123}]
>>> [i for n, i in enumerate(d) if i not in d[n + 1:]]
[{'b': 123}, {'a': 123}]

ここではdict比較を使用できるので、最初のリストの残りの部分にない要素のみを保持します(この概念にはインデックスからのみアクセスできるnため、を使用しますenumerate)。


2
これは、最初の回答と比較したリストで構成される辞書のリストでも機能します
gbozee

1
これは、トップの回答とは異なり、辞書の値としてハッシュできないタイプがある場合にも機能します。
Steve Rossiter、2016

1
ここでは、目的はキーではなく重複する値を削除することです。この回答のコードを参照してください
Jamil Noyda

これは非常に非効率的なコードです。辞書のif i not in d[n + 1:]リスト全体を反復処理します(nただし、操作の総数を半分にするだけです)。このチェックは、辞書のすべての要素に対して行われるため、このコードはO(n ^ 2)の時間の複雑さです
Boris

辞書を値として持つ辞書では機能しません
Roko Mijic

22

逆シリアル化されたJSONオブジェクトなどのネストされた辞書を操作している場合、他の回答は機能しません。この場合、以下を使用できます。

import json
set_of_jsons = {json.dumps(d, sort_keys=True) for d in X}
X = [json.loads(t) for t in set_of_jsons]

1
すごい!トリックは、dictオブジェクトをセットに直接追加できないことです。dump()によってdictオブジェクトをjsonオブジェクトに変換する必要があります。
Reihan_amn

19

サードパーティのパッケージを使用しても問題ない場合は、次を使用できますiteration_utilities.unique_everseen

>>> from iteration_utilities import unique_everseen
>>> l = [{'a': 123}, {'b': 123}, {'a': 123}]
>>> list(unique_everseen(l))
[{'a': 123}, {'b': 123}]

これは、元のリストの順序を保持し、UTは、(遅いアルゴリズムにフォールバックすることにより、辞書のような非ハッシュアイテムを扱うことができ、元のリストの要素としている元のリストの代わりにユニークな要素が)。キーと値の両方がハッシュ可能である場合、その関数の引数を使用して、「一意性テスト」用のハッシュ可能アイテムを作成できます(そのため、で機能します)。O(n*m)nmO(n)keyO(n)

ディクショナリ(順序とは無関係に比較される)の場合は、次のように比較する別のデータ構造にマップする必要がありますfrozenset

>>> list(unique_everseen(l, key=lambda item: frozenset(item.items())))
[{'a': 123}, {'b': 123}]

tuple等しい辞書は必ずしも同じ順序であるとは限らない(絶対順序ではなく挿入順序が保証されているPython 3.7でも)ため、単純なアプローチ(並べ替えなし)を使用しないでください。

>>> d1 = {1: 1, 9: 9}
>>> d2 = {9: 9, 1: 1}
>>> d1 == d2
True
>>> tuple(d1.items()) == tuple(d2.items())
False

また、キーがソート可能でない場合、タプルのソートでさえ機能しない可能性があります。

>>> d3 = {1: 1, 'a': 'a'}
>>> tuple(sorted(d3.items()))
TypeError: '<' not supported between instances of 'str' and 'int'

基準

これらのアプローチのパフォーマンスがどのように比較されるかを確認することは有用であると考えたので、小さなベンチマークを行いました。ベンチマークグラフは、重複を含まないリストに基づくリストサイズに対する時間です(任意に選択したもので、重複をいくつか追加したり、多数追加しても、ランタイムは大幅に変更されません)。これは対数-対数プロットなので、すべての範囲がカバーされます。

絶対的な時間:

ここに画像の説明を入力してください

最速のアプローチに関連するタイミング:

ここに画像の説明を入力してください

ここで、thethethethetheからの2番目のアプローチが最速です。関数を使用したunique_everseenアプローチkeyは2番目ですが、順序を維持するのが最速のアプローチです。他からのアプローチjcolladothefourtheyeはほぼ同じ速度です。unique_everseenキーなしで使用するアプローチとEmmanuelScorpilのソリューションは、長いリストの場合は非常に遅く、のO(n*n)代わりに動作がはるかに悪くなりますO(n)stpkのアプローチjsonはそうではありませんO(n*n)が、同様のO(n)アプローチよりもはるかに低速です。

ベンチマークを再現するコード:

from simple_benchmark import benchmark
import json
from collections import OrderedDict
from iteration_utilities import unique_everseen

def jcollado_1(l):
    return [dict(t) for t in {tuple(d.items()) for d in l}]

def jcollado_2(l):
    seen = set()
    new_l = []
    for d in l:
        t = tuple(d.items())
        if t not in seen:
            seen.add(t)
            new_l.append(d)
    return new_l

def Emmanuel(d):
    return [i for n, i in enumerate(d) if i not in d[n + 1:]]

def Scorpil(a):
    b = []
    for i in range(0, len(a)):
        if a[i] not in a[i+1:]:
            b.append(a[i])

def stpk(X):
    set_of_jsons = {json.dumps(d, sort_keys=True) for d in X}
    return [json.loads(t) for t in set_of_jsons]

def thefourtheye_1(data):
    return OrderedDict((frozenset(item.items()),item) for item in data).values()

def thefourtheye_2(data):
    return {frozenset(item.items()):item for item in data}.values()

def iu_1(l):
    return list(unique_everseen(l))

def iu_2(l):
    return list(unique_everseen(l, key=lambda inner_dict: frozenset(inner_dict.items())))

funcs = (jcollado_1, Emmanuel, stpk, Scorpil, thefourtheye_1, thefourtheye_2, iu_1, jcollado_2, iu_2)
arguments = {2**i: [{'a': j} for j in range(2**i)] for i in range(2, 12)}
b = benchmark(funcs, arguments, 'list size')

%matplotlib widget
import matplotlib as mpl
import matplotlib.pyplot as plt
plt.style.use('ggplot')
mpl.rcParams['figure.figsize'] = '8, 6'

b.plot(relative_to=thefourtheye_2)

完全を期すために、重複のみを含むリストのタイミングを次に示します。

# this is the only change for the benchmark
arguments = {2**i: [{'a': 1} for j in range(2**i)] for i in range(2, 12)}

ここに画像の説明を入力してください

機能unique_everseenなしのkey場合を除いて、タイミングは大幅に変化しません。この場合は、これが最速のソリューションです。:それのランタイムは、リスト内で一意の値の量に依存するため、しかし、それは非ハッシュ値とその機能のためだけに最高のケース(そう代表ではない)ですO(n*m)。この場合にはわずか1であるので、それが中で実行されますO(n)


免責事項:私はの作者ですiteration_utilities


15

時々、古いスタイルのループがまだ有用です。このコードはjcolladoのコードより少し長いですが、非常に読みやすいです:

a = [{'a': 123}, {'b': 123}, {'a': 123}]
b = []
for i in range(0, len(a)):
    if a[i] not in a[i+1:]:
        b.append(a[i])

0中にはrange(0, len(a))必要ありません。
ファンアントニオ

12

注文を保持したい場合は、

from collections import OrderedDict
print OrderedDict((frozenset(item.items()),item) for item in data).values()
# [{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}]

順序が重要でない場合は、行うことができます

print {frozenset(item.items()):item for item in data}.values()
# [{'a': 3222, 'b': 1234}, {'a': 123, 'b': 1234}]

注:Python 3では、2番目のアプローチではdict_values、リストの代わりにシリアライズ不可能な出力が提供されます。全部をもう一度リストにキャストする必要があります。list(frozen.....)
saran3h

12

ワークフローでパンダを使用している場合の1つのオプションは、辞書のリストをpd.DataFrameコンストラクターに直接フィードすることです。次に、必要な結果drop_duplicatesto_dict得るためにとメソッドを使用します。

import pandas as pd

d = [{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}, {'a': 123, 'b': 1234}]

d_unique = pd.DataFrame(d).drop_duplicates().to_dict('records')

print(d_unique)

[{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}]

3

普遍的な答えはありませんが、次のようにリストが何らかのキーでソートされている場合:

l=[{'a': {'b': 31}, 't': 1},
   {'a': {'b': 31}, 't': 1},
 {'a': {'b': 145}, 't': 2},
 {'a': {'b': 25231}, 't': 2},
 {'a': {'b': 25231}, 't': 2}, 
 {'a': {'b': 25231}, 't': 2}, 
 {'a': {'b': 112}, 't': 3}]

ソリューションは次のように簡単です。

import itertools
result = [a[0] for a in itertools.groupby(l)]

結果:

[{'a': {'b': 31}, 't': 1},
{'a': {'b': 145}, 't': 2},
{'a': {'b': 25231}, 't': 2},
{'a': {'b': 112}, 't': 3}]

ネストされた辞書で機能し、(明らかに)順序を保持します。


1

セットを使用できますが、辞書をハッシュ可能なタイプに変換する必要があります。

seq = [{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}, {'a': 123, 'b': 1234}]
unique = set()
for d in seq:
    t = tuple(d.iteritems())
    unique.add(t)

ユニークな今と等しい

set([(('a', 3222), ('b', 1234)), (('a', 123), ('b', 1234))])

辞書を取り戻すには:

[dict(x) for x in unique]

の順序d.iteritems()は保証されていないため、で「重複」する可能性がありますunique
danodonovan

-1

(@Emmanuelのソリューションに基づく)二重にネストされたリスト内包表記を使用した簡単な1行のソリューションを次に示します。

これは、a辞書全体が一致するかどうかをチェックするのではなく、主キーとして各辞書の単一のキー(たとえば)を使用します

[i for n, i in enumerate(list_of_dicts) if i.get(primary_key) not in [y.get(primary_key) for y in list_of_dicts[n + 1:]]]

それはOPが要求したものではありませんが、それが私をこのスレッドに導いた原因です。


-1

短くはないが読みやすい:

list_of_data = [{'a': 123}, {'b': 123}, {'a': 123}]

list_of_data_uniq = []
for data in list_of_data:
    if data not in list_of_data_uniq:
        list_of_data_uniq.append(data)

これで、リストlist_of_data_uniqに一意の辞書があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.