Python - Elenco di dizionari unici


158

Diciamo che ho un elenco di dizionari:

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

e ho bisogno di ottenere un elenco di dizionari univoci (rimuovendo i duplicati):

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

Qualcuno può aiutarmi con il modo più efficiente per raggiungere questo obiettivo in Python?


5
Quanto sono estesi questi dizionari? Hai bisogno di un controllo individuale degli attributi per determinare i duplicati o è sufficiente un solo valore al loro interno?
gddc,

Questi dadi hanno ottenuto 8 chiavi: coppie di valori e l'elenco ha ottenuto 200 dadi. Hanno effettivamente ottenuto un ID ed è sicuro per me rimuovere il dict dall'elenco se il valore ID trovato è un duplicato.
Limaaf,


Risposte:


238

Quindi crea un dict temporaneo con la chiave come id. Questo filtra i duplicati. Ilvalues() dict sarà la lista

In Python2.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'}]

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

In Python2.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 - come si può usare lo stesso per rimuovere dizione da un elenco basato su più attributi, l'ho provato ma sembra non funzionare> {v ['flight'] ['lon'] ['lat']: v for v in stream} .values ​​()
Jorge Vidinha,

1
@JorgeVidinha supponendo che ciascuno possa essere castato su str (o unicode), prova questo: {str(v['flight'])+':'+str(v['lon'])+','+str(v['lat']): v for v in stream}.values()questo crea semplicemente una chiave unica basata sui tuoi valori. Mi piace'MH370:-21.474370,86.325589'
whunterknight il

4
@JorgeVidinha, puoi usare una tupla come chiave del dizionario{(v['flight'], v['lon'], v['lat']): v for v in stream}.values()
John La Rooy,

si noti che ciò può alterare l'ordine dei dizionari nell'elenco! utilizzare OrderedDictda collections list(OrderedDict((v['id'], v) for v in L).values()) o ordinare la lista risultante se funziona meglio per voi
gevra

Se hai bisogno di considerare tutti i valori e non solo l'ID che puoi usare list({str(i):i for i in L}.values())Qui usiamo str (i) per creare una stringa univoca che rappresenta il dizionario che viene usato per filtrare i duplicati.
DelboyJay

79

Il solito modo di trovare solo gli elementi comuni in un set è usare la setclasse di Python . Basta aggiungere tutti gli elementi al set, quindi convertire il set in a liste bam i duplicati sono spariti.

Il problema, ovviamente, è che a set()può contenere solo voci hash e a dictnon è hash.

Se avessi questo problema, la mia soluzione sarebbe quella di convertire ciascuno dictin una stringa che rappresenta il dict, quindi aggiungere tutte le stringhe a un set()quindi leggere i valori della stringa come a list()e convertire nuovamente indict .

Una buona rappresentazione di una dictforma in stringa è il formato JSON. E Python ha un modulo integrato per JSON (chiamato jsonovviamente).

Il problema rimanente è che gli elementi in a dictnon sono ordinati e quando Python converte la dictstringa in una stringa JSON, potresti ottenere due stringhe JSON che rappresentano dizionari equivalenti ma non sono stringhe identiche. La soluzione semplice è passare l'argomento sort_keys=Truequando chiami json.dumps().

EDIT: questa soluzione presupponeva che un dato dictpotesse avere qualsiasi parte diversa. Se possiamo supporre che ognuno dictcon lo stesso "id"valore corrisponderà a vicenda dictcon lo stesso"id" valore, allora questo è eccessivo; La soluzione di @ gnibbler sarebbe più veloce e più facile.

EDIT: Ora c'è un commento di André Lima che dice esplicitamente che se l'ID è un duplicato, si può presumere che l'intero dictsia un duplicato. Quindi questa risposta è eccessiva e raccomando la risposta di @ gnibbler.


Grazie per l'aiuto steveha. La tua risposta in realtà mi ha dato alcune conoscenze che non avevo, da quando ho appena iniziato con Python =)
Limaaf

1
Anche se eccessivo dato l'ID in questo caso particolare, questa è ancora una risposta eccellente!
Josh Werts,

8
Questo mi aiuta dal momento che il mio dizionario non ha una chiave ed è identificato in modo univoco da tutte le sue voci. Grazie!
ericso,

Questa soluzione funziona la maggior parte delle volte, ma potrebbero esserci problemi di prestazioni con il ridimensionamento, ma penso che l'autore lo sappia e quindi raccomanda la soluzione con "id". Preoccupazioni relative alle prestazioni: questa soluzione utilizza la serializzazione per la stringa e quindi la deserializzazione ... la serializzazione / deserializzazione è un calcolo costoso e di solito non si espande bene (il numero di elementi è n> 1e6 o ogni dizionario contiene> 1e6 elementi o entrambi) o se si dispone di per eseguire questo molte volte> 1e6 o spesso.
Trevor Boyd Smith,

Proprio come una scorciatoia questa soluzione illustra un ottimo esempio canonico del perché vorresti progettare la tua soluzione ... cioè se hai un ID unico ... allora puoi accedere in modo efficiente ai dati ... se sei pigro e non hai un ID, quindi l'accesso ai tuoi dati è più costoso.
Trevor Boyd Smith,

21

Nel caso in cui i dizionari siano identificati in modo univoco da tutti gli elementi (ID non disponibile) è possibile utilizzare la risposta utilizzando JSON. Di seguito è un'alternativa che non utilizza JSON e funzionerà fino a quando tutti i valori del dizionario sono immutabili

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

19

Puoi usare la libreria numpy (funziona solo per Python2.x):

   import numpy as np 

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

Per farlo funzionare con Python 3.x (e le versioni recenti di numpy), è necessario convertire un array di dicts in un numpy array di stringhe, ad es.

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

13
Ottieni l'errore TypeError: unorderable types: dict() > dict()quando lo fai in Python 3.5.
Guillochon,

16

Ecco una soluzione ragionevolmente compatta, anche se sospetto non particolarmente efficiente (per dirla in parole povere):

>>> 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
Circonda la map()chiamata con list()in Python 3 per recuperare un elenco, altrimenti è un mapoggetto.
dmn

un ulteriore vantaggio di questo approccio in Python 3.6+ è che l'ordinamento delle liste viene preservato
jnnnnn,

7

Dato che idè sufficiente per rilevare duplicati e idhash: eseguili attraverso un dizionario che ha idcome chiave. Il valore per ogni chiave è il dizionario originale.

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

In Python 3, values()non restituisce un elenco; dovrai avvolgere l'intero lato destro di quell'espressione list()e puoi scrivere la carne dell'espressione in modo più economico come comprensione dettata:

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

Si noti che il risultato probabilmente non sarà nello stesso ordine dell'originale. Se questo è un requisito, è possibile utilizzare un Collections.OrderedDictanziché un dict.

Per inciso, può essere sensato conservare i dati in un dizionario che utilizza la idchiave as per cominciare.


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)

uscite:

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


Nello stesso esempio. come posso ottenere i dicts contenenti solo gli ID simili?
user8162

@ user8162, come vorresti che fosse l'output?
Yusuf X,

A volte, avrò lo stesso ID, ma un'età diversa. quindi l'output sarà [{'age': [34, 40], 'id': 1, 'name': ['john', Peter]}]. In breve, se gli ID sono uguali, quindi combinare i contenuti degli altri in un elenco come ho menzionato qui. Grazie in anticipo.
user8162,

1
b = {x ['id']: [y per y in a se y ['id'] == x ['id']] per x in a} è un modo per raggrupparli insieme.
Yusuf X,

4

Espandendo sulla risposta John La Rooy ( Python - Elenco di dizionari unici ), rendendolo un po 'più flessibile:

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())

Funzione chiamante:

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

4

Possiamo farcela 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'}]

Notare leggermente diverso dalla risposta accettata.

drop_duplicates controllerà tutta la colonna in panda, se tutti uguali la riga verrà eliminata.

Per esempio :

Se cambiamo il secondo dictnome da John a 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

In python 3.6+ (quello che ho testato), basta usare:

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)

Spiegazione: stiamo mappando il json.dumpscodice per codificare i dizionari come oggetti json, che sono immutabili. setpuò quindi essere utilizzato per produrre un iterabile di immutabili unici . Infine, riconvertiamo la nostra rappresentazione del dizionario usando json.loads. Si noti che inizialmente è necessario ordinare in base alle chiavi per disporre i dizionari in una forma univoca. Questo è valido per Python 3.6+ poiché i dizionari sono ordinati per impostazione predefinita.


1
Ricorda di ordinare le chiavi prima di scaricare su JSON. Inoltre non è necessario convertirsi in listprima di farlo set.
Nathan,

2

Ho riassunto i miei preferiti per provare:

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

Una soluzione rapida è semplicemente generando un nuovo elenco.

sortedlist = []

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

1

Non so se vuoi solo che l'id delle tue parole nell'elenco sia unico, ma se l'obiettivo è quello di avere una serie di dict in cui l'unicità sia sui valori di tutte le chiavi .. dovresti usare la chiave delle tuple in questo modo nella tua comprensione:

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

Spero che aiuti te o un'altra persona a preoccuparti ...


1

Ci sono molte risposte qui, quindi vorrei aggiungerne un'altra:

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

Opzione piuttosto semplice:

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

Bene, tutte le risposte menzionate qui sono buone, ma in alcune risposte si possono riscontrare errori se gli elementi del dizionario hanno un elenco o un dizionario nidificati, quindi propongo una risposta semplice

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

-1

Ecco un'implementazione con poca memoria overhead al costo di non essere compatta come il resto.

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

produzione:

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

1
Devi testarlo un po 'di più. La modifica dell'elenco durante l'iterazione potrebbe non funzionare sempre come previsto
John La Rooy,

@gnibbler ottimo punto! Eliminerò la risposta e la testerò più a fondo.
Samy Vilar,

Sembra migliore. È possibile utilizzare un set per tenere traccia degli ID anziché del dict. Prendi in considerazione di iniziare indexa len(values)e contare indietro, ciò significa che puoi sempre diminuire indexse tu delo no. es.for index in reversed(range(len(values))):
John La Rooy,

@gnibbler interessante, i set hanno quasi una costante ricerca di dizionari?
Samy Vilar,

-4

Questa è la soluzione che ho trovato:

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

Fondamentalmente si controlla se l'ID è presente nell'elenco, se lo è, eliminare il dizionario, in caso contrario, aggiungere l'ID all'elenco


Userei un set piuttosto che un elenco per usedID. È una ricerca più veloce e più leggibile
happydave il

Sì, non sapevo dei set ... ma sto imparando ... Stavo solo guardando la risposta di @gnibbler ...
tabchas

1
Devi testarlo un po 'di più. La modifica dell'elenco durante l'iterazione potrebbe non funzionare sempre come previsto
John La Rooy

Sì, non capisco perché non funziona ... Qualche idea su cosa sto facendo di sbagliato?
tabchas

No, ho colto il problema ... è solo che non capisco perché sta dando quel problema ... lo sai?
tabchas
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.