Python - Lista unikalnych słowników


158

Powiedzmy, że mam listę słowników:

[
    {'id': 1, 'name': 'john', 'age': 34},
    {'id': 1, 'name': 'john', 'age': 34},
    {'id': 2, 'name': 'hanna', 'age': 30},
]

i muszę uzyskać listę unikalnych słowników (usuwanie duplikatów):

[
    {'id': 1, 'name': 'john', 'age': 34},
    {'id': 2, 'name': 'hanna', 'age': 30},
]

Czy ktoś może mi pomóc w najbardziej efektywnym sposobie osiągnięcia tego w Pythonie?


5
Jak obszerne są te słowniki? Czy potrzebujesz sprawdzania poszczególnych atrybutów, aby określić duplikaty, czy też wystarczy sprawdzenie jednej wartości?
gddc

Te słowa mają 8 par klucz: wartość, a lista ma 200 słów. W rzeczywistości otrzymali identyfikator i mogę bezpiecznie usunąć dyktowanie z listy, jeśli znaleziona wartość identyfikatora jest duplikatem.
Limaaf


Odpowiedzi:


238

Zrób więc tymczasowy dykt, w którym klucz będzie id. To odfiltrowuje duplikaty. Plikvalues()Z dict będzie lista

W Pythonie 2.7

>>> L=[
... {'id':1,'name':'john', 'age':34},
... {'id':1,'name':'john', 'age':34},
... {'id':2,'name':'hanna', 'age':30},
... ]
>>> {v['id']:v for v in L}.values()
[{'age': 34, 'id': 1, 'name': 'john'}, {'age': 30, 'id': 2, 'name': 'hanna'}]

W Python3

>>> L=[
... {'id':1,'name':'john', 'age':34},
... {'id':1,'name':'john', 'age':34},
... {'id':2,'name':'hanna', 'age':30},
... ] 
>>> list({v['id']:v for v in L}.values())
[{'age': 34, 'id': 1, 'name': 'john'}, {'age': 30, 'id': 2, 'name': 'hanna'}]

W Pythonie 2.5 / 2.6

>>> L=[
... {'id':1,'name':'john', 'age':34},
... {'id':1,'name':'john', 'age':34},
... {'id':2,'name':'hanna', 'age':30},
... ] 
>>> dict((v['id'],v) for v in L).values()
[{'age': 34, 'id': 1, 'name': 'john'}, {'age': 30, 'id': 2, 'name': 'hanna'}]

@John La Rooy - jak można użyć tego samego do usunięcia słowników z listy opartej na wielu atrybutach, próbowałem tego, ale wydaje się, że nie działa> {v ['flight'] ['lon'] ['lat']: v dla v w strumieniu} .values ​​()
Jorge Vidinha

1
@JorgeVidinha zakładając, że każdy może być rzutowany na str (lub unicode), spróbuj tego: {str(v['flight'])+':'+str(v['lon'])+','+str(v['lat']): v for v in stream}.values()To po prostu tworzy unikalny klucz oparty na twoich wartościach. Jak'MH370:-21.474370,86.325589'
whunterknight

4
@JorgeVidinha, możesz użyć krotki jako klucza słownika{(v['flight'], v['lon'], v['lat']): v for v in stream}.values()
John La Rooy,

pamiętaj, że może to zmienić kolejność słowników na liście! korzystania OrderedDictz collections list(OrderedDict((v['id'], v) for v in L).values()) lub sortowanie listy wynikowej, czy działa lepiej dla ciebie
gevra

Jeśli potrzebujesz wziąć pod uwagę wszystkie wartości, a nie tylko ID, którego możesz użyć. list({str(i):i for i in L}.values())Tutaj używamy str (i) do stworzenia unikalnego ciągu, który reprezentuje słownik, który jest używany do filtrowania duplikatów.
DelboyJay

79

Zwykłym sposobem znalezienia tylko wspólnych elementów w zestawie jest użycie setklasy Pythona . Po prostu dodaj wszystkie elementy do zestawu, a następnie przekonwertuj zestaw na a listi bam, że duplikaty zniknęły.

Problem polega oczywiście na tym, że a set()może zawierać tylko wpisy z funkcją hashowania, a a dictnie może zawierać hashable.

Gdybym miał ten problem, moim rozwiązaniem byłoby przekonwertowanie każdego z nich dictna ciąg, który reprezentuje dict, a następnie dodanie wszystkich ciągów do a, set()a następnie odczytanie wartości ciągu jako a list()i konwersja z powrotem do dict.

Dobrą reprezentacją a dictw postaci ciągu jest format JSON. A Python ma wbudowany moduł dla JSON (nazywany jsonoczywiście).

Pozostały problem polega na tym, że elementy w a dictnie są uporządkowane, a gdy Python konwertuje dictciąg na ciąg JSON, możesz otrzymać dwa ciągi JSON, które reprezentują równoważne słowniki, ale nie są identycznymi ciągami. Prostym rozwiązaniem jest przekazanie argumentu sort_keys=Truepodczas wywołania json.dumps().

EDYCJA: To rozwiązanie zakładało, że dana część dictmoże mieć inną część. Jeśli możemy założyć, że każdy dicto tej samej "id"wartości będzie pasował do wszystkich dicto tej samej "id"wartości, to jest to przesada; Rozwiązanie @ gnibbler byłoby szybsze i łatwiejsze.

EDYCJA: Teraz jest komentarz André Limy, który wyraźnie mówi, że jeśli identyfikator jest duplikatem, można bezpiecznie założyć, że całość dictjest duplikatem. Więc ta odpowiedź jest przesada i polecam odpowiedź @ gnibbler.


Dzięki za pomoc steveha. Twoja odpowiedź faktycznie dała mi pewną wiedzę, której nie miałem, odkąd dopiero zacząłem z Pythonem =)
Limaaf

1
Chociaż przesada biorąc pod uwagę identyfikator w tym konkretnym przypadku, jest to nadal doskonała odpowiedź!
Josh Werts,

8
Pomaga mi to, ponieważ mój słownik nie ma klucza i jest jednoznacznie identyfikowany tylko przez wszystkie jego wpisy. Dzięki!
ericso

To rozwiązanie działa przez większość czasu, ale mogą wystąpić problemy z wydajnością przy skalowaniu w górę, ale autor wydaje mi się, że to wie i dlatego zaleca rozwiązanie z "id". Problemy z wydajnością: to rozwiązanie wykorzystuje serializację do ciągu, a następnie deserializację ... serializacja / deserializacja jest kosztownym obliczeniem i zwykle nie jest dobrze skalowana (liczba elementów wynosi n> 1e6 lub każdy słownik zawiera> 1e6 elementów lub oba) lub jeśli masz aby wykonać to wiele razy> 1e6 lub często.
Trevor Boyd Smith

Krótko mówiąc, to rozwiązanie ilustruje świetny kanoniczny przykład, dlaczego chciałbyś zaprojektować swoje rozwiązanie ... tj. Jeśli masz unikalny identyfikator ... wtedy możesz efektywnie uzyskać dostęp do danych ... jeśli jesteś leniwy i nie masz identyfikatora, wtedy dostęp do danych jest droższy.
Trevor Boyd Smith

21

W przypadku, gdy słowniki są jednoznacznie identyfikowane przez wszystkie pozycje (identyfikator nie jest dostępny), możesz użyć odpowiedzi za pomocą JSON. Poniżej przedstawiono alternatywę, która nie używa formatu JSON i będzie działać, o ile wszystkie wartości słownika są niezmienne

[dict(s) for s in set(frozenset(d.items()) for d in L)]

19

Możesz użyć biblioteki numpy (działa tylko dla Python2.x):

   import numpy as np 

   list_of_unique_dicts=list(np.unique(np.array(list_of_dicts)))

Aby zadziałało z Pythonem 3.x (i ostatnimi wersjami numpy), musisz przekonwertować tablicę dykt na tablicę ciągów numpy, np.

list_of_unique_dicts=list(np.unique(np.array(list_of_dicts).astype(str)))

13
Uzyskaj błąd TypeError: unorderable types: dict() > dict(), robiąc to w Pythonie 3.5.
Guillochon

16

Oto dość kompaktowe rozwiązanie, choć podejrzewam, że nie jest szczególnie wydajne (delikatnie mówiąc):

>>> ds = [{'id':1,'name':'john', 'age':34},
...       {'id':1,'name':'john', 'age':34},
...       {'id':2,'name':'hanna', 'age':30}
...       ]
>>> map(dict, set(tuple(sorted(d.items())) for d in ds))
[{'age': 30, 'id': 2, 'name': 'hanna'}, {'age': 34, 'id': 1, 'name': 'john'}]

3
Otocz map()wywołanie list()w Pythonie 3, aby otrzymać listę z powrotem, w przeciwnym razie jest to mapobiekt.
dmn,

dodatkową zaletą tego podejścia w Pythonie 3.6+ jest to, że kolejność list jest zachowana
jnnnnn

7

Ponieważ idjest wystarczający do wykrywania duplikatów, a idjest hashable: przepuść je przez słownik, który ma idjako klucz. Wartością każdego klucza jest oryginalny słownik.

deduped_dicts = dict((item["id"], item) for item in list_of_dicts).values()

W Pythonie 3 values()nie zwraca listy; musisz zawinąć całą prawą stronę tego wyrażenia list(), a treść wyrażenia możesz zapisać bardziej ekonomicznie jako dyktowanie:

deduped_dicts = list({item["id"]: item for item in list_of_dicts}.values())

Zwróć uwagę, że wynik prawdopodobnie nie będzie w tej samej kolejności, co oryginał. Jeśli jest to wymagane, możesz użyć Collections.OrderedDictzamiast dict.

Na marginesie, sensowne może być po prostu przechowywanie danych w słowniku, w którym na początku jest używany idklucz as.


6
a = [
{'id':1,'name':'john', 'age':34},
{'id':1,'name':'john', 'age':34},
{'id':2,'name':'hanna', 'age':30},
]

b = {x['id']:x for x in a}.values()

print(b)

wyjścia:

[{'age': 34, 'id': 1, 'name': 'john'}, {'age': 30, 'id': 2, 'name': 'hanna'}]


W tym samym przykładzie. jak mogę uzyskać dykty zawierające tylko podobne identyfikatory?
user8162

@ user8162, jak chciałbyś, aby wyglądało to wyjście?
Yusuf X

Czasami będę mieć ten sam dowód, ale inny wiek. więc wyjście to [{'wiek': [34, 40], 'id': 1, 'imię': ['jan', Piotr]}]. Krótko mówiąc, jeśli identyfikatory są takie same, połącz zawartość innych w listę, o której wspomniałem tutaj. Z góry dziękuję.
user8162

1
b = {x ['id']: [y for y in a if y ['id'] == x ['id']] for x in a} to jeden ze sposobów na pogrupowanie ich razem.
Yusuf X

4

Rozszerzając odpowiedź Johna La Rooy'a ( Python - Lista unikalnych słowników ), czyniąc ją nieco bardziej elastyczną:

def dedup_dict_list(list_of_dicts: list, columns: list) -> list:
    return list({''.join(row[column] for column in columns): row
                for row in list_of_dicts}.values())

Funkcja dzwonienia:

sorted_list_of_dicts = dedup_dict_list(
    unsorted_list_of_dicts, ['id', 'name'])

4

Możemy zrobić pandas

import pandas as pd
yourdict=pd.DataFrame(L).drop_duplicates().to_dict('r')
Out[293]: [{'age': 34, 'id': 1, 'name': 'john'}, {'age': 30, 'id': 2, 'name': 'hanna'}]

Uwaga nieco różni się od akceptowanej odpowiedzi.

drop_duplicates sprawdzi wszystkie kolumny w pandach, jeśli wszystkie są takie same, wiersz zostanie usunięty.

Na przykład :

Jeśli zmienimy drugie dictimię z jan na peter

L=[
    {'id': 1, 'name': 'john', 'age': 34},
    {'id': 1, 'name': 'peter', 'age': 34},
    {'id': 2, 'name': 'hanna', 'age': 30},
]
pd.DataFrame(L).drop_duplicates().to_dict('r')
Out[295]: 
[{'age': 34, 'id': 1, 'name': 'john'},
 {'age': 34, 'id': 1, 'name': 'peter'},# here will still keeping the dict in the out put 
 {'age': 30, 'id': 2, 'name': 'hanna'}]

2

W Pythonie 3.6+ (co testowałem) wystarczy użyć:

import json

#Toy example, but will also work for your case 
myListOfDicts = [{'a':1,'b':2},{'a':1,'b':2},{'a':1,'b':3}]
#Start by sorting each dictionary by keys
myListOfDictsSorted = [sorted(d.items()) for d in myListOfDicts]

#Using json methods with set() to get unique dict
myListOfUniqueDicts = list(map(json.loads,set(map(json.dumps, myListOfDictsSorted))))

print(myListOfUniqueDicts)

Wyjaśnienie: mapujemy, json.dumpsaby zakodować słowniki jako obiekty json, które są niezmienne. setmoże być następnie użyty do stworzenia iterowalnych unikatowych niezmiennych. Na koniec konwertujemy z powrotem do naszej reprezentacji słownikowej za pomocą json.loads. Zwróć uwagę, że początkowo należy posortować według kluczy, aby uporządkować słowniki w unikalnej formie. Dotyczy to Pythona 3.6+, ponieważ słowniki są uporządkowane domyślnie.


1
Pamiętaj, aby posortować klucze przed zrzuceniem do formatu JSON. Nie musisz też konwertować na listprzed wykonaniem set.
Nathan

2

Podsumowałem swoje ulubione do wypróbowania:

https://repl.it/@SmaMa/Python-List-of-unique-dictionaries

# ----------------------------------------------
# Setup
# ----------------------------------------------

myList = [
  {"id":"1", "lala": "value_1"},
  {"id": "2", "lala": "value_2"}, 
  {"id": "2", "lala": "value_2"}, 
  {"id": "3", "lala": "value_3"}
]
print("myList:", myList)

# -----------------------------------------------
# Option 1 if objects has an unique identifier
# -----------------------------------------------

myUniqueList = list({myObject['id']:myObject for myObject in myList}.values())
print("myUniqueList:", myUniqueList)

# -----------------------------------------------
# Option 2 if uniquely identified by whole object
# -----------------------------------------------

myUniqueSet = [dict(s) for s in set(frozenset(myObject.items()) for myObject in myList)]
print("myUniqueSet:", myUniqueSet)

# -----------------------------------------------
# Option 3 for hashable objects (not dicts)
# -----------------------------------------------

myHashableObjects = list(set(["1", "2", "2", "3"]))
print("myHashAbleList:", myHashableObjects)

1

Szybkim i łatwym rozwiązaniem jest po prostu wygenerowanie nowej listy.

sortedlist = []

for item in listwhichneedssorting:
    if item not in sortedlist:
        sortedlist.append(item)

1

Nie wiem, czy chcesz, aby tylko identyfikatory twoich dykt na liście były unikalne, ale jeśli celem jest posiadanie zestawu dykt, w którym niepowtarzalność jest na wartościach wszystkich kluczy ... powinieneś użyć klucza krotek w ten sposób w twoim zrozumieniu:

>>> L=[
...     {'id':1,'name':'john', 'age':34},
...    {'id':1,'name':'john', 'age':34}, 
...    {'id':2,'name':'hanna', 'age':30},
...    {'id':2,'name':'hanna', 'age':50}
...    ]
>>> len(L)
4
>>> L=list({(v['id'], v['age'], v['name']):v for v in L}.values())
>>>L
[{'id': 1, 'name': 'john', 'age': 34}, {'id': 2, 'name': 'hanna', 'age': 30}, {'id': 2, 'name': 'hanna', 'age': 50}]
>>>len(L)
3

Mam nadzieję, że pomoże to tobie lub innej osobie, która ma obawy ....


1

Odpowiedzi jest tutaj wiele, więc pozwól mi dodać kolejne:

import json
from typing import List

def dedup_dicts(items: List[dict]):
    dedupped = [ json.loads(i) for i in set(json.dumps(item, sort_keys=True) for item in items)]
    return dedupped

items = [
    {'id': 1, 'name': 'john', 'age': 34},
    {'id': 1, 'name': 'john', 'age': 34},
    {'id': 2, 'name': 'hanna', 'age': 30},
]
dedup_dicts(items)

0

Całkiem prosta opcja:

L = [
    {'id':1,'name':'john', 'age':34},
    {'id':1,'name':'john', 'age':34},
    {'id':2,'name':'hanna', 'age':30},
    ]


D = dict()
for l in L: D[l['id']] = l
output = list(D.values())
print output

0

Cóż, wszystkie wymienione tutaj odpowiedzi są dobre, ale w niektórych odpowiedziach można napotkać błąd, jeśli elementy słownika mają zagnieżdżoną listę lub słownik, więc proponuję prostą odpowiedź

a = [str(i) for i in a]
a = list(set(a))
a = [eval(i) for i in a]

-1

Oto implementacja z niewielkim narzutem pamięci, kosztem mniejszego rozmiaru niż reszta.

values = [ {'id':2,'name':'hanna', 'age':30},
           {'id':1,'name':'john', 'age':34},
           {'id':1,'name':'john', 'age':34},
           {'id':2,'name':'hanna', 'age':30},
           {'id':1,'name':'john', 'age':34},]
count = {}
index = 0
while index < len(values):
    if values[index]['id'] in count:
        del values[index]
    else:
        count[values[index]['id']] = 1
        index += 1

wynik:

[{'age': 30, 'id': 2, 'name': 'hanna'}, {'age': 34, 'id': 1, 'name': 'john'}]

1
Musisz to nieco przetestować. Modyfikowanie listy podczas iteracji może nie zawsze działać zgodnie z oczekiwaniami
John La Rooy

@gnibbler bardzo dobra uwaga! Skasuję odpowiedź i dokładniej ją przetestuję.
Samy Vilar

Wygląda lepiej. Możesz użyć zestawu do śledzenia identyfikatorów zamiast dyktowania. Rozważ rozpoczęcie od indexat len(values)i liczenie wstecz, co oznacza, że ​​zawsze możesz zmniejszyć wartość, indexczy delnie. np.for index in reversed(range(len(values))):
John La Rooy

@gnibbler ciekawe, czy zestawy mają prawie ciągły wygląd jak słowniki?
Samy Vilar

-4

Oto rozwiązanie, które znalazłem:

usedID = []

x = [
{'id':1,'name':'john', 'age':34},
{'id':1,'name':'john', 'age':34},
{'id':2,'name':'hanna', 'age':30},
]

for each in x:
    if each['id'] in usedID:
        x.remove(each)
    else:
        usedID.append(each['id'])

print x

Zasadniczo sprawdzasz, czy identyfikator jest obecny na liście, jeśli tak, usuń słownik, jeśli nie, dołącz identyfikator do listy


Użyłbym zestawu zamiast listy dla usedID. To szybsze wyszukiwanie i bardziej czytelne
happydave

Tak, nie wiedziałem o zestawach ... ale się uczę ... Właśnie patrzyłem na odpowiedź @gnibbler ...
tabchas

1
Musisz to nieco przetestować. Modyfikowanie listy podczas iteracji może nie zawsze działać zgodnie z oczekiwaniami
John La Rooy

Tak, nie rozumiem, dlaczego to nie działa ... Jakieś pomysły, co robię źle?
tabchas

Nie, złapałem problem ... po prostu nie rozumiem, dlaczego powoduje ten problem ... wiesz?
tabchas
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.