Wenn die Verwendung eines Pakets eines Drittanbieters in Ordnung wäre, können Sie Folgendes verwenden 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}]
Es behält die Reihenfolge der ursprünglichen Liste bei und ut kann auch nicht zerlegbare Elemente wie Wörterbücher verarbeiten, indem es auf einen langsameren Algorithmus zurückgreift ( O(n*m)
wobei n
die Elemente in der ursprünglichen Liste und m
die eindeutigen Elemente in der ursprünglichen Liste statt sind O(n)
). Wenn sowohl Schlüssel als auch Werte hashbar sind, können Sie das key
Argument dieser Funktion verwenden, um hashbare Elemente für den "Eindeutigkeitstest" zu erstellen (damit es funktioniertO(n)
).
Im Fall eines Wörterbuchs (das unabhängig von der Reihenfolge vergleicht) müssen Sie es einer anderen Datenstruktur zuordnen, die so vergleichbar ist, zum Beispiel frozenset
:
>>> list(unique_everseen(l, key=lambda item: frozenset(item.items())))
[{'a': 123}, {'b': 123}]
Beachten Sie, dass Sie keinen einfachen tuple
Ansatz verwenden sollten (ohne zu sortieren), da gleiche Wörterbücher nicht unbedingt dieselbe Reihenfolge haben müssen (selbst in Python 3.7, wo die Einfügereihenfolge - nicht die absolute Reihenfolge - garantiert ist):
>>> d1 = {1: 1, 9: 9}
>>> d2 = {9: 9, 1: 1}
>>> d1 == d2
True
>>> tuple(d1.items()) == tuple(d2.items())
False
Und selbst das Sortieren des Tupels funktioniert möglicherweise nicht, wenn die Schlüssel nicht sortierbar sind:
>>> d3 = {1: 1, 'a': 'a'}
>>> tuple(sorted(d3.items()))
TypeError: '<' not supported between instances of 'str' and 'int'
Benchmark
Ich dachte, es könnte nützlich sein zu sehen, wie die Leistung dieser Ansätze verglichen wird, also habe ich einen kleinen Benchmark durchgeführt. Die Benchmark-Diagramme sind Zeit vs. Listengröße basierend auf einer Liste ohne Duplikate (die willkürlich ausgewählt wurde, ändert sich die Laufzeit nicht wesentlich, wenn ich einige oder viele Duplikate hinzufüge). Es ist ein Log-Log-Plot, sodass der gesamte Bereich abgedeckt ist.
Die absoluten Zeiten:
Die Zeiten relativ zum schnellsten Ansatz:
Der zweite Ansatz vom vierten Auge ist hier am schnellsten. Der unique_everseen
Ansatz mit der key
Funktion steht an zweiter Stelle, ist jedoch der schnellste Ansatz, der die Ordnung bewahrt. Die anderen Ansätze von jcollado und thefourtheye sind fast genauso schnell. Der Ansatz unique_everseen
ohne Schlüssel und die Lösungen von Emmanuel und Scorpil sind für längere Listen sehr langsam und verhalten sich O(n*n)
stattdessen viel schlechter O(n)
. stpks Ansatz mit json
ist nicht, O(n*n)
aber es ist viel langsamer als die ähnlichen O(n)
Ansätze.
Der Code zum Reproduzieren der Benchmarks:
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)
Der Vollständigkeit halber ist hier der Zeitpunkt für eine Liste angegeben, die nur Duplikate enthält:
# 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)}
Die Timings ändern sich nicht wesentlich, außer unique_everseen
ohne key
Funktion, was in diesem Fall die schnellste Lösung ist. Dies ist jedoch nur der beste Fall (also nicht repräsentativ) für diese Funktion mit nicht zerlegbaren Werten, da ihre Laufzeit von der Anzahl der eindeutigen Werte in der Liste abhängt: O(n*m)
In diesem Fall ist sie nur 1 und wird daher ausgeführt O(n)
.
Haftungsausschluss: Ich bin der Autor von iteration_utilities
.