Python - Lista de diccionarios únicos

158

Digamos que tengo una lista de diccionarios:

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

y necesito obtener una lista de diccionarios únicos (eliminando los duplicados):

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

¿Alguien puede ayudarme con la forma más eficiente de lograr esto en Python?

Limaaf
fuente
55
¿Cuán extensos son estos diccionarios? ¿Necesita verificación de atributos individuales para determinar duplicados, o es suficiente con un solo valor?
gddc
Estos dictos obtuvieron 8 claves: pares de valores y la lista obtuvo 200 dictos. De hecho, obtuvieron una ID y es seguro para mí eliminar el dict de la lista si el valor de ID encontrado es un duplicado.
Limaaf
Forzenset es una opción efectiva. set(frozenset(i.items()) for i in list)
Abhijeet

Respuestas:

238

Así que haga un dict temporal con la clave siendo id. Esto filtra los duplicados. El values()del dict será la lista

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

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

En 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
fuente
@John La Rooy: ¿cómo podría uno usar lo mismo para eliminar los diccionarios de una lista basada en múltiples atributos? Intenté esto pero parece que no funciona> {v ['flight'] ['lon'] ['lat']: v para v in stream} .values ​​()
Jorge Vidinha
1
@JorgeVidinha suponiendo que cada uno se pueda convertir en str (o unicode), intente esto: {str(v['flight'])+':'+str(v['lon'])+','+str(v['lat']): v for v in stream}.values()esto solo crea una clave única basada en sus valores. Me gusta'MH370:-21.474370,86.325589'
whunterknight
44
@JorgeVidinha, puedes usar una tupla como clave del diccionario{(v['flight'], v['lon'], v['lat']): v for v in stream}.values()
John La Rooy
¡tenga en cuenta que esto puede alterar el orden de los diccionarios en la lista! utilizar OrderedDictde collections list(OrderedDict((v['id'], v) for v in L).values()) u ordenar la lista resultante si que funciona mejor para usted
gevra
Si necesita considerar todos los valores y no solo la ID, puede usar list({str(i):i for i in L}.values())Aquí usamos str (i) para crear una cadena única que represente el diccionario que se usa para filtrar los duplicados.
DelboyJay
79

La forma habitual de encontrar solo los elementos comunes en un conjunto es usar la setclase de Python . Simplemente agregue todos los elementos al conjunto, luego convierta el conjunto a a list, y bam, los duplicados se han ido.

El problema, por supuesto, es que a set()solo puede contener entradas hashables, y a dictno es hashaable.

Si tuviera este problema, mi solución sería convertir cada uno dicten una cadena que represente el dict, luego agregar todas las cadenas a y set()luego leer los valores de la cadena como a list()y convertirlos nuevamente dict.

Una buena representación de una dicten forma de cadena es el formato JSON. Y Python tiene un módulo incorporado para JSON (llamado, jsonpor supuesto).

El problema restante es que los elementos en a dictno están ordenados, y cuando Python convierte dicta una cadena JSON, puede obtener dos cadenas JSON que representan diccionarios equivalentes pero que no son cadenas idénticas. La solución fácil es pasar el argumento sort_keys=Truecuando llamas json.dumps().

EDITAR: Esta solución suponía que un determinado dictpodría tener una parte diferente. Si podemos suponer que cada uno dictcon el mismo "id"valor coincidirá entre sí dictcon el mismo "id"valor, entonces esto es exagerado; La solución de @ gnibbler sería más rápida y fácil.

EDITAR: Ahora hay un comentario de André Lima que dice explícitamente que si la identificación es un duplicado, es seguro asumir que el todo dictes un duplicado. Entonces esta respuesta es exagerada y recomiendo la respuesta de @ gnibbler.

steveha
fuente
Gracias por la ayuda steveha. Su respuesta realmente me dio algunos conocimientos que no tenía, ya que recién comencé con Python =)
Limaaf
1
Si bien la identificación en este caso particular es excesiva, ¡esta sigue siendo una excelente respuesta!
Josh Werts
8
Esto me ayuda, ya que mi diccionario no tiene una clave y solo se identifica de forma única por todas sus entradas. ¡Gracias!
ericso
Esta solución funciona la mayor parte del tiempo, pero puede haber problemas de rendimiento con la ampliación, pero creo que el autor lo sabe y, por lo tanto, recomienda la solución con "id". Problemas de rendimiento: esta solución utiliza la serialización para encadenar y luego la deserialización ... la serialización / deserialización es un cálculo costoso y generalmente no se escala bien (el número de elementos es n> 1e6 o cada diccionario contiene> 1e6 elementos o ambos) o si tiene ejecutar esto muchas veces> 1e6 o con frecuencia.
Trevor Boyd Smith
Como una breve reseña, esta solución ilustra un gran ejemplo canónico de por qué querría diseñar su solución ... es decir, si tiene una identificación única ... entonces puede acceder de manera eficiente a los datos ... si es vago y no tiene una identificación, entonces su acceso a datos es más costoso.
Trevor Boyd Smith
21

En caso de que los diccionarios estén identificados únicamente por todos los elementos (el ID no está disponible), puede usar la respuesta con JSON. La siguiente es una alternativa que no utiliza JSON y funcionará siempre que todos los valores del diccionario sean inmutables

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

Puede usar la biblioteca numpy (solo funciona para Python2.x):

   import numpy as np 

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

Para que funcione con Python 3.x (y versiones recientes de numpy), debe convertir una matriz de dictos en una matriz de cadenas numpy, por ejemplo

list_of_unique_dicts=list(np.unique(np.array(list_of_dicts).astype(str)))
burbuja
fuente
13
Obtenga el error TypeError: unorderable types: dict() > dict()al hacer esto en Python 3.5.
Guillochon
16

Aquí hay una solución razonablemente compacta, aunque sospecho que no es particularmente eficiente (por decirlo suavemente):

>>> 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'}]
Greg E.
fuente
3
Rodea la map()llamada list()en Python 3 para recuperar una lista, de lo contrario es un mapobjeto.
dmn
Un beneficio adicional de este enfoque en Python 3.6+ es que la lista de pedidos se conserva
jnnnnn
7

Dado que ides suficiente para detectar duplicados y ides hashable: ejecútelos a través de un diccionario que tenga idla clave. El valor de cada clave es el diccionario original.

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

En Python 3, values()no devuelve una lista; necesitará envolver todo el lado derecho de esa expresión list(), y puede escribir la carne de la expresión de manera más económica como una comprensión dict:

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

Tenga en cuenta que el resultado probablemente no estará en el mismo orden que el original. Si ese es un requisito, puede usar un en Collections.OrderedDictlugar de undict .

Por otro lado, puede tener mucho sentido mantener los datos en un diccionario que utiliza la idtecla como para comenzar.

un poco
fuente
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)

salidas:

[{'edad': 34, 'id': 1, 'nombre': 'john'}, {'edad': 30, 'id': 2, 'nombre': 'hanna'}]

Yusuf X
fuente
En el mismo ejemplo. ¿Cómo puedo obtener los dicts que contienen solo las ID similares?
user8162
@ user8162, ¿cómo desea que se vea la salida?
Yusuf X
A veces, tendré la misma identificación, pero diferente edad. entonces la salida será [{'edad': [34, 40], 'id': 1, 'nombre': ['john', Peter]}]. En resumen, si las ID son las mismas, combine el contenido de otros en una lista como mencioné aquí. Gracias por adelantado.
user8162
1
b = {x ['id']: [y para y en a si y ['id'] == x ['id']] para x en a} es una forma de agruparlos.
Yusuf X
4

Ampliando John La Rooy ( respuesta de Python - Lista de diccionarios únicos ), haciéndola un poco más flexible:

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

Función de llamada:

sorted_list_of_dicts = dedup_dict_list(
    unsorted_list_of_dicts, ['id', 'name'])
Operador ilegal
fuente
4

Podemos hacer con 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'}]

Observe un poco diferente de la respuesta de aceptación.

drop_duplicates comprobará todas las columnas en pandas, si todo es igual, la fila se descartará.

Por ejemplo :

Si cambiamos el segundo dictnombre de 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'}]
YOBEN_S
fuente
2

En python 3.6+ (lo que he probado), solo use:

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)

Explicación: estamos mapeando json.dumpspara codificar los diccionarios como objetos json, que son inmutables. setentonces puede usarse para producir un iterable de inmutables únicos . Finalmente, convertimos nuevamente a nuestra representación de diccionario usando json.loads. Tenga en cuenta que inicialmente, uno debe ordenar por teclas para organizar los diccionarios en una forma única. Esto es válido para Python 3.6+ ya que los diccionarios están ordenados por defecto.

VanillaSpinIce
fuente
1
Recuerde ordenar las claves antes de volcar a JSON. Tampoco necesita convertir a listantes de hacerlo set.
Nathan
2

He resumido mis favoritos para probar:

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)
Sma Ma
fuente
1

Una solución rápida y sucia es simplemente generando una nueva lista.

sortedlist = []

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

No sé si solo desea que la identificación de sus dictados en la lista sea única, pero si el objetivo es tener un conjunto de dict donde la unicidad está en los valores de todas las claves ... debe usar la clave de tuplas como esta en tu comprensión:

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

Espero que te ayude a ti u otra persona que tenga la preocupación ...

nixmind
fuente
1

Aquí hay muchas respuestas, así que permítanme agregar otra:

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)
Monkut
fuente
0

Opción bastante sencilla:

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
jedwards
fuente
0

Bueno, todas las respuestas mencionadas aquí son buenas, pero en algunas respuestas uno puede enfrentar un error si los elementos del diccionario tienen una lista anidada o un diccionario, por lo que propongo una respuesta simple

a = [str(i) for i in a]
a = list(set(a))
a = [eval(i) for i in a]
PRAKHAR KAUSHIK
fuente
-1

Heres una implementación con poca sobrecarga de memoria a costa de no ser tan compacta como el 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

salida:

[{'age': 30, 'id': 2, 'name': 'hanna'}, {'age': 34, 'id': 1, 'name': 'john'}]
Samy Vilar
fuente
1
Necesitas probar esto un poco más. La modificación de la lista mientras está iterando puede que no siempre funcione como espera
John La Rooy
@gnibbler muy buen punto! Eliminaré la respuesta y la probaré más a fondo.
Samy Vilar
Se ve mejor. Puede usar un conjunto para realizar un seguimiento de los identificadores en lugar del dict. Considere comenzar indexen at len(values)y contar hacia atrás, eso significa que siempre puede disminuir indexsi usted delo no. Por ejemplofor index in reversed(range(len(values))):
John La Rooy
@gnibbler interesante, ¿los conjuntos tienen una búsqueda casi constante como los diccionarios?
Samy Vilar
-4

Esta es la solución que encontré:

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

Básicamente verifica si la ID está presente en la lista, si es así, elimine el diccionario, si no, agregue la ID a la lista

tabchas
fuente
Usaría un conjunto en lugar de una lista para usedID. Es una búsqueda más rápida y más legible
happydave
Sí, no sabía sobre sets ... pero estoy aprendiendo ... Estaba mirando la respuesta de @gnibbler ...
tabchas
1
Necesitas probar esto un poco más. La modificación de la lista mientras está iterando podría no funcionar siempre como espera
John La Rooy
Sí, no entiendo por qué no funciona ... ¿Alguna idea de lo que estoy haciendo mal?
tabchas
No, entendí el problema ... es solo que no entiendo por qué está dando ese problema ... ¿sabes?
tabchas