Python - Liste de dictionnaires uniques

158

Disons que j'ai une liste de dictionnaires:

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

et j'ai besoin d'obtenir une liste de dictionnaires uniques (en supprimant les doublons):

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

Quelqu'un peut-il m'aider avec le moyen le plus efficace d'y parvenir en Python?

Limaaf
la source
5
Quelle est l'étendue de ces dictionnaires? Avez-vous besoin d'une vérification individuelle des attributs pour déterminer les doublons, ou la vérification d'une seule valeur suffit-elle?
gddc
Ces dictionnaires ont 8 paires clé: valeur et la liste a 200 dictionnaires. Ils ont en fait un identifiant et je peux supprimer le dict de la liste en toute sécurité si la valeur d'identifiant trouvée est un doublon.
Limaaf
forzenset est une option efficace. set(frozenset(i.items()) for i in list)
Abhijeet

Réponses:

238

Alors faites un dict temporaire avec la clé étant le id. Cela filtre les doublons. levalues() du dict sera la liste

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
la source
@John La Rooy - comment pourrait-on utiliser la même chose pour supprimer des dictionnaires d'une liste basée sur plusieurs attributs, essayé mais semble ne pas fonctionner> {v ['flight'] ['lon'] ['lat']: v pour v dans le flux} .values ​​()
Jorge Vidinha
1
@JorgeVidinha en supposant que chacun peut être converti en str (ou en unicode), essayez ceci: {str(v['flight'])+':'+str(v['lon'])+','+str(v['lat']): v for v in stream}.values()Cela crée simplement une clé unique basée sur vos valeurs. J'aime'MH370:-21.474370,86.325589'
whunterknight
4
@JorgeVidinha, vous pouvez utiliser un tuple comme clé de dictionnaire{(v['flight'], v['lon'], v['lat']): v for v in stream}.values()
John La Rooy
notez que cela peut modifier l'ordre des dictionnaires dans la liste! utiliser à OrderedDictpartir collections list(OrderedDict((v['id'], v) for v in L).values()) ou trier la liste des résultats si cela fonctionne mieux pour vous
gevra
Si vous avez besoin de toutes les valeurs et pas seulement de l'ID que vous pouvez utiliser list({str(i):i for i in L}.values())Ici, nous utilisons str (i) pour créer une chaîne unique qui représente le dictionnaire utilisé pour filtrer les doublons.
DelboyJay
79

La façon habituelle de trouver uniquement les éléments communs dans un ensemble est d'utiliser la setclasse de Python . Ajoutez simplement tous les éléments à l'ensemble, puis convertissez l'ensemble en a list, et bam les doublons ont disparu.

Le problème, bien sûr, est que a set()ne peut contenir que des entrées hachables, et a dictn'est pas hachable.

Si j'avais ce problème, ma solution serait de convertir chacun dicten une chaîne qui représente le dict, puis d'ajouter toutes les chaînes à a, set()puis de lire les valeurs de chaîne en tant que a list()et de les reconvertir endict .

Une bonne représentation d'un dictsous forme de chaîne est le format JSON. Et Python a un module intégré pour JSON (appelé jsonbien sûr).

Le problème restant est que les éléments de a dictne sont pas ordonnés, et lorsque Python convertit le dicten une chaîne JSON, vous pouvez obtenir deux chaînes JSON qui représentent des dictionnaires équivalents mais ne sont pas des chaînes identiques. La solution simple est de passer l'argument sort_keys=Truelorsque vous appelez json.dumps().

EDIT: Cette solution supposait qu'une donnée dictpouvait avoir n'importe quelle partie différente. Si nous pouvons supposer que tous dictavec la même "id"valeur correspondront à tous les autres dictavec la même "id"valeur, alors c'est exagéré; La solution de @ gnibbler serait plus rapide et plus simple.

EDIT: Maintenant, il y a un commentaire d'André Lima disant explicitement que si l'ID est un double, il est prudent de supposer que le tout dictest un double. Donc, cette réponse est exagérée et je recommande la réponse de @ gnibbler.

Steveha
la source
Merci pour l'aide steveha. Votre réponse m'a en fait donné des connaissances que je n'avais pas, puisque je viens de commencer avec Python =)
Limaaf
1
Bien que excessif étant donné l'ID dans ce cas particulier, c'est toujours une excellente réponse!
Josh Werts
8
Cela m'aide car mon dictionnaire n'a pas de clé et n'est identifié que de manière unique par toutes ses entrées. Merci!
ericso
Cette solution fonctionne la plupart du temps mais il peut y avoir des problèmes de performances lors de la mise à l'échelle, mais je pense que l'auteur le sait et recommande donc la solution avec "id". Problèmes de performances: cette solution utilise la sérialisation en chaîne, puis la désérialisation ... la sérialisation / désérialisation est un calcul coûteux et ne s'adapte généralement pas bien (le nombre d'éléments est n> 1e6 ou chaque dictionnaire contient> 1e6 éléments ou les deux) ou si vous avez pour l'exécuter plusieurs fois> 1e6 ou souvent.
Trevor Boyd Smith
Tout comme en bref, cette solution illustre un excellent exemple canonique de la raison pour laquelle vous voudriez concevoir votre solution ... c'est-à-dire si vous avez un identifiant unique ... alors vous pouvez accéder efficacement aux données ... si vous êtes paresseux et n'avez pas d'identifiant, votre accès aux données est plus cher.
Trevor Boyd Smith
21

Dans le cas où les dictionnaires ne sont identifiés que de manière unique par tous les éléments (l'ID n'est pas disponible), vous pouvez utiliser la réponse en utilisant JSON. Ce qui suit est une alternative qui n'utilise pas JSON et fonctionnera tant que toutes les valeurs du dictionnaire sont immuables

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

Vous pouvez utiliser la bibliothèque numpy (fonctionne uniquement pour Python2.x):

   import numpy as np 

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

Pour le faire fonctionner avec Python 3.x (et les versions récentes de numpy), vous devez convertir un tableau de dictées en tableau numpy de chaînes, par exemple

list_of_unique_dicts=list(np.unique(np.array(list_of_dicts).astype(str)))
bulle
la source
13
Obtenez l'erreur TypeError: unorderable types: dict() > dict()en faisant cela dans Python 3.5.
Guillochon le
16

Voici une solution raisonnablement compacte, même si je soupçonne qu'elle n'est pas particulièrement efficace (pour le moins dire):

>>> 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.
la source
3
Entourez l' map()appel avec list()dans Python 3 pour récupérer une liste, sinon c'est un mapobjet.
dmn le
un avantage supplémentaire de cette approche dans python 3.6+ est que l'ordre des listes est préservé
jnnnnn
7

Puisque le idest suffisant pour détecter les doublons et qu'il idest hachable: exécutez-les dans un dictionnaire qui a idcomme clé. La valeur de chaque clé est le dictionnaire d'origine.

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

En Python 3, values()ne renvoie pas de liste; vous aurez besoin d'envelopper tout le côté droit de cette expression list(), et vous pouvez écrire la viande de l'expression de manière plus économique comme une compréhension de dict:

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

Notez que le résultat ne sera probablement pas dans le même ordre que l'original. Si c'est une exigence, vous pouvez utiliser un Collections.OrderedDictau lieu d'un dict.

En passant, il peut être très judicieux de simplement conserver les données dans un dictionnaire qui utilise la idclé as pour commencer.

kindall
la source
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)

les sorties:

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

Yusuf X
la source
Dans le même exemple. comment puis-je obtenir les dictionnaires contenant uniquement les identifiants similaires?
user8162
@ user8162, à quoi voudriez-vous que la sortie ressemble?
Yusuf X
Parfois, j'aurai la même pièce d'identité, mais un âge différent. donc la sortie doit être [{'age': [34, 40], 'id': 1, 'name': ['john', Peter]}]. En bref, si les identifiants sont identiques, combinez le contenu des autres dans une liste comme je l'ai mentionné ici. Merci d'avance.
user8162
1
b = {x ['id']: [y pour y dans a if y ['id'] == x ['id']] pour x dans a} est une façon de les regrouper.
Yusuf X
4

Extension de la réponse de John La Rooy ( Python - Liste de dictionnaires uniques ), ce qui la rend un peu plus 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())

Fonction d'appel:

sorted_list_of_dicts = dedup_dict_list(
    unsorted_list_of_dicts, ['id', 'name'])
Opérateur illégal
la source
4

On peut faire avec 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'}]

Remarquez légèrement différent de la réponse d'acceptation.

drop_duplicates vérifiera toutes les colonnes des pandas, si elles sont toutes identiques, la ligne sera supprimée.

Par exemple :

Si nous changeons le 2ème dictnom de John en 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
la source
2

En python 3.6+ (ce que j'ai testé), utilisez simplement:

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)

Explication: nous mappons le json.dumpspour encoder les dictionnaires en tant qu'objets json, qui sont immuables. setpeut ensuite être utilisé pour produire un itérable d' immuables uniques . Enfin, nous retournons à notre représentation de dictionnaire en utilisant json.loads. Notez qu'au départ, il faut trier par clés pour disposer les dictionnaires sous une forme unique. Ceci est valable pour Python 3.6+ puisque les dictionnaires sont classés par défaut.

VanilleSpinIce
la source
1
N'oubliez pas de trier les clés avant de transférer vers JSON. Vous n'avez pas non plus besoin de vous convertir listavant de le faire set.
Nathan
2

J'ai résumé mes favoris pour essayer:

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
la source
1

Une solution rapide et sale consiste simplement à générer une nouvelle liste.

sortedlist = []

for item in listwhichneedssorting:
    if item not in sortedlist:
        sortedlist.append(item)
Lyzazel
la source
1

Je ne sais pas si vous voulez seulement que l'identifiant de vos dictés dans la liste soit unique, mais si le but est d'avoir un ensemble de dict où l'unicité est sur les valeurs de toutes les clés ... vous devez utiliser la clé tuples comme celle-ci dans votre compréhension:

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

J'espère que cela vous aidera ou aidera une autre personne à vous inquiéter ....

nixmind
la source
1

Il y a beaucoup de réponses ici, alors laissez-moi en ajouter une autre:

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
la source
0

Option assez simple:

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
la source
0

Eh bien, toutes les réponses mentionnées ici sont bonnes, mais dans certaines réponses, on peut faire face à une erreur si les éléments du dictionnaire ont une liste ou un dictionnaire imbriqués, donc je propose une réponse simple

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

Voici une implémentation avec peu de surcharge de mémoire au prix de ne pas être aussi compacte que le reste.

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

production:

[{'age': 30, 'id': 2, 'name': 'hanna'}, {'age': 34, 'id': 1, 'name': 'john'}]
Samy Vilar
la source
1
Vous devez tester cela un peu plus. Modifier la liste pendant que vous l'itérez peut ne pas toujours fonctionner comme prévu
John La Rooy
@gnibbler très bon point! Je supprimerai la réponse et la testerai plus en détail.
Samy Vilar
Regarde mieux. Vous pouvez utiliser un ensemble pour garder une trace des identifiants au lieu du dict. Pensez à commencer indexpar len(values)et à compter à rebours, cela signifie que vous pouvez toujours décrémenter, indexque vous soyez delou non. egfor index in reversed(range(len(values))):
John La Rooy
@gnibbler intéressant, est-ce que les ensembles ont une recherche presque constante comme des dictionnaires?
Samy Vilar
-4

Voici la solution que j'ai trouvée:

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

En gros, vous vérifiez si l'ID est présent dans la liste, si c'est le cas, supprimez le dictionnaire, sinon, ajoutez l'ID à la liste

tabchas
la source
J'utiliserais un ensemble plutôt qu'une liste pour usedID. C'est une recherche plus rapide et plus lisible
happydave
Oui, je ne savais pas à propos des ensembles ... mais j'apprends ... Je regardais juste la réponse @gnibbler ...
tabchas
1
Vous devez tester cela un peu plus. Modifier la liste pendant que vous l'itérez peut ne pas toujours fonctionner comme prévu
John La Rooy
Oui, je ne comprends pas pourquoi ça ne marche pas ... Des idées sur ce que je fais mal?
tabchas
Non, j'ai attrapé le problème ... c'est juste que je ne comprends pas pourquoi il pose ce problème ... le savez-vous?
tabchas