Entfernen Sie doppelte Diktate in der Liste in Python

153

Ich habe eine Liste von Diktaten und möchte die Diktate mit identischen Schlüssel- und Wertepaaren entfernen.

Für diese Liste: [{'a': 123}, {'b': 123}, {'a': 123}]

Ich möchte dies zurückgeben: [{'a': 123}, {'b': 123}]

Ein anderes Beispiel:

Für diese Liste: [{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}, {'a': 123, 'b': 1234}]

Ich möchte dies zurückgeben: [{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}]

Brenden
quelle
Können Sie uns mehr über das eigentliche Problem erzählen, das Sie lösen möchten? Dies scheint ein seltsames Problem zu sein.
Glück
Ich kombiniere ein paar Listen von Diktaten und es gibt Duplikate. Also muss ich diese Duplikate entfernen.
Brenden
Ich fand eine Lösung in stackoverflow.com/questions/480214/… in einer Antwort ohne die Verwendung vonset()
Sebastian Wagner

Antworten:

242

Versuche dies:

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

Die Strategie besteht darin, die Liste der Wörterbücher in eine Liste der Tupel zu konvertieren, in der die Tupel die Elemente des Wörterbuchs enthalten. Da die Tupel gehasht werden können, können Sie Duplikate mit entfernen set(unter Verwendung eines festgelegten Verständnisses wäre hier eine ältere Python-Alternative set(tuple(d.items()) for d in l)) und anschließend die Wörterbücher aus Tupeln mit neu erstellendict .

wo:

  • l ist die ursprüngliche Liste
  • d ist eines der Wörterbücher in der Liste
  • t ist eines der aus einem Wörterbuch erstellten Tupel

Bearbeiten: Wenn Sie die Bestellung beibehalten möchten, funktioniert der Einzeiler oben nicht, da dies setnicht der Fall ist . Mit ein paar Codezeilen können Sie dies jedoch auch tun:

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

Beispielausgabe:

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

Hinweis: Wie von @alexis hervorgehoben, kann es vorkommen, dass zwei Wörterbücher mit denselben Schlüsseln und Werten nicht zu demselben Tupel führen. Dies kann passieren, wenn sie einen anderen Verlauf zum Hinzufügen / Entfernen von Schlüsseln durchlaufen. Wenn dies bei Ihrem Problem der Fall ist, sollten Sie die von ihm vorgeschlagene Sortierung in Betracht ziehen d.items().

jcollado
quelle
35
Gute Lösung, aber es gibt einen Fehler: Es d.items()wird nicht garantiert, dass Elemente in einer bestimmten Reihenfolge zurückgegeben werden. Sie sollten tuple(sorted(d.items()))sicherstellen, dass Sie keine unterschiedlichen Tupel für dieselben Schlüssel-Wert-Paare erhalten.
Alexis
@alexis Ich habe ein paar Tests gemacht und du hast in der Tat Recht. Wenn viele Schlüssel dazwischen hinzugefügt und später entfernt werden, kann dies der Fall sein. Vielen Dank für Ihren Kommentar.
JCollado
Cool. Ich habe den Fix zu Ihrer Antwort hinzugefügt, um zukünftigen Lesern zu helfen, die möglicherweise nicht die gesamte Konversation lesen.
Alexis
2
Beachten Sie, dass dies nicht funktioniert, wenn Sie in diese Liste von Diktaten aus einem jsonModul wie ich
laden
2
Dies ist eine gültige Lösung in diesem Fall, funktioniert aber nicht bei verschachtelten Wörterbüchern
Lorenzo Belli
51

Ein weiterer Einzeiler, der auf Listenverständnissen basiert:

>>> 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}]

Da wir hier dictVergleiche verwenden können, behalten wir nur die Elemente bei, die nicht in der restlichen Anfangsliste enthalten sind (auf diesen Begriff kann nur über den Index zugegriffen werden n, daher die Verwendung von enumerate).

Emmanuel
quelle
2
Dies funktioniert auch für eine Liste von Wörterbüchern, die aus Listen bestehen, verglichen mit der ersten Antwort
gbozee
1
Dies funktioniert auch, wenn Sie im Gegensatz zur Top-Antwort möglicherweise einen nicht zerlegbaren Typ als Wert in Ihren Wörterbüchern haben.
Steve Rossiter
1
Hier besteht der Zweck darin, doppelte Werte zu entfernen, nicht den Schlüssel. Siehe den Code dieser Antwort
Jamil Noyda,
Dies ist sehr ineffizienter Code. if i not in d[n + 1:]iteriert über die gesamte Liste der Diktate (von, naber das halbiert nur die Gesamtzahl der Operationen) und Sie führen diese Prüfung für jedes Element in Ihrem Wörterbuch durch, so dass dieser Code O (n ^ 2) Zeitkomplexität ist
Boris
funktioniert nicht für Wörterbücher mit Wörterbüchern als Werten
Roko Mijic
22

Andere Antworten funktionieren nicht, wenn Sie mit verschachtelten Wörterbüchern wie deserialisierten JSON-Objekten arbeiten. Für diesen Fall könnten Sie verwenden:

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]
stpk
quelle
1
Toll! Der Trick besteht darin, dass das dict-Objekt nicht direkt zu einer Menge hinzugefügt werden kann, sondern von dump () in ein json-Objekt konvertiert werden muss.
Reihan_amn
18

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 ndie Elemente in der ursprünglichen Liste und mdie eindeutigen Elemente in der ursprünglichen Liste statt sind O(n)). Wenn sowohl Schlüssel als auch Werte hashbar sind, können Sie das keyArgument 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 tupleAnsatz 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:

Geben Sie hier die Bildbeschreibung ein

Die Zeiten relativ zum schnellsten Ansatz:

Geben Sie hier die Bildbeschreibung ein

Der zweite Ansatz vom vierten Auge ist hier am schnellsten. Der unique_everseenAnsatz mit der keyFunktion 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_everseenohne 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 jsonist 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)}

Geben Sie hier die Bildbeschreibung ein

Die Timings ändern sich nicht wesentlich, außer unique_everseenohne keyFunktion, 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.

MSeifert
quelle
15

Manchmal sind Loops im alten Stil immer noch nützlich. Dieser Code ist etwas länger als der von jcollado, aber sehr einfach zu lesen:

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])
Skorpil
quelle
Das 0In range(0, len(a))ist nicht notwendig.
Juan Antonio
12

Wenn Sie die Bestellung beibehalten möchten, können Sie dies tun

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

Wenn die Reihenfolge keine Rolle spielt, können Sie dies tun

print {frozenset(item.items()):item for item in data}.values()
# [{'a': 3222, 'b': 1234}, {'a': 123, 'b': 1234}]
thefourtheye
quelle
Hinweis: In Python 3 liefert Ihr zweiter Ansatz eine nicht serialisierbare dict_valuesAusgabe anstelle einer Liste. Sie müssen das Ganze erneut in eine Liste aufnehmen. list(frozen.....)
Saran3h
12

Wenn Sie Pandas in Ihrem Workflow verwenden, können Sie eine Liste von Wörterbüchern direkt an den pd.DataFrameKonstruktor senden. Verwenden Sie dann drop_duplicatesund to_dictMethoden für das gewünschte Ergebnis.

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}]
jpp
quelle
3

Keine universelle Antwort , aber wenn Ihre Liste nach einem Schlüssel sortiert ist , wie folgt:

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}]

dann ist die Lösung so einfach wie:

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

Ergebnis:

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

Arbeitet mit verschachtelten Wörterbüchern und bewahrt (offensichtlich) die Ordnung.

Highstaker
quelle
1

Sie können ein Set verwenden, aber Sie müssen die Diktate in einen Hash-Typ verwandeln.

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)

Einzigartig ist jetzt gleich

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

So erhalten Sie Diktate zurück:

[dict(x) for x in unique]
Matimus
quelle
Die Reihenfolge von d.iteritems()ist nicht garantiert - daher kann es vorkommen, dass Sie "Duplikate" erhalten unique.
Danodonovan
-1

Hier ist eine schnelle einzeilige Lösung mit einem doppelt verschachtelten Listenverständnis (basierend auf der Lösung von @Emmanuel).

Dabei wird ain jedem Diktat ein einzelner Schlüssel (z. B. ) als Primärschlüssel verwendet, anstatt zu prüfen, ob das gesamte Diktat übereinstimmt

[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:]]]

Es ist nicht das, wonach OP gefragt hat, aber es hat mich zu diesem Thread gebracht, also dachte ich mir, ich würde die Lösung veröffentlichen, mit der ich am Ende endete

Alec
quelle
-1

Nicht so kurz, aber leicht zu lesen:

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)

Jetzt hat die Liste list_of_data_uniqeindeutige Diktate.

user1723157
quelle