サードパーティのパッケージを使用しても問題ない場合は、次を使用できます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)
n
m
O(n)
key
O(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番目ですが、順序を維持するのが最速のアプローチです。他からのアプローチjcolladoとthefourtheyeはほぼ同じ速度です。unique_everseen
キーなしで使用するアプローチとEmmanuelとScorpilのソリューションは、長いリストの場合は非常に遅く、の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
。