J'ai une liste de dict, et j'aimerais supprimer les dict avec des paires de clé et de valeur identiques.
Pour cette liste: [{'a': 123}, {'b': 123}, {'a': 123}]
Je voudrais rendre cela: [{'a': 123}, {'b': 123}]
Un autre exemple:
Pour cette liste: [{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}, {'a': 123, 'b': 1234}]
Je voudrais rendre cela: [{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}]
Essaye ça:
[dict(t) for t in {Tuple(d.items()) for d in l}]
La stratégie consiste à convertir la liste des dictionnaires en une liste de n-uplets où les n-uplets contiennent les éléments du dictionnaire. Comme les n-uplets peuvent être hachés, vous pouvez supprimer les doublons en utilisant set
(en utilisant un compréhension du je ici, ancien python une alternative serait set(Tuple(d.items()) for d in l)
) et ensuite, recréez les dictionnaires à partir de n-uplets avec dict
.
où:
l
est la liste d'origined
est l'un des dictionnaires de la listet
est l'un des n-uplets créés à partir d'un dictionnaireEdit: Si vous voulez conserver l'ordre, le one-liner ci-dessus ne fonctionnera pas car set
ne le fera pas. Cependant, avec quelques lignes de code, vous pouvez également le faire:
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
Exemple de sortie:
[{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}]
Remarque: comme l'a souligné @alexis, il peut arriver que deux dictionnaires contenant les mêmes clés et valeurs ne génèrent pas le même tuple. Cela pourrait se produire s’ils passent par un historique différent d’ajout/suppression de clés. Si tel est le cas pour votre problème, envisagez de trier d.items()
comme il le suggère.
Un autre one-liner basé sur les compréhensions de liste:
>>> 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}]
Ici, puisque nous pouvons utiliser dict
comparaison, nous ne conservons que les éléments qui ne figurent pas dans le reste de la liste initiale (cette notion n’est accessible que par l’index n
, d’où l’utilisation de enumerate
).
Parfois, les boucles à l'ancienne sont encore utiles. Ce code est un peu plus long que celui de jcollado, mais très facile à lire:
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])
Les autres réponses ne fonctionneraient pas si vous utilisiez des dictionnaires imbriqués tels que des objets JSON désérialisés. Pour ce cas, vous pouvez utiliser:
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]
Si vous voulez préserver l'ordre, alors vous pouvez faire
from collections import OrderedDict
print OrderedDict((frozenset(item.items()),item) for item in data).values()
# [{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}]
Si l'ordre n'a pas d'importance, alors vous pouvez le faire
print {frozenset(item.items()):item for item in data}.values()
# [{'a': 3222, 'b': 1234}, {'a': 123, 'b': 1234}]
Si vous utilisiez un paquet tiers, vous pourriez utiliser 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}]
Il préserve l'ordre de la liste d'origine et ut peut également gérer des éléments inutiles tels que les dictionnaires en utilisant un algorithme plus lent (O(n*m)
où n
sont les éléments de la liste d'origine et m
les éléments uniques de la liste d'origine à la place de O(n)
). Dans le cas où les clés et les valeurs sont haschables, vous pouvez utiliser l'argument key
de cette fonction pour créer des éléments haschables pour le "uniqueness-test" (afin que cela fonctionne dans O(n)
).
Dans le cas d'un dictionnaire (qui compare indépendamment de l'ordre), vous devez le mapper sur une autre structure de données qui se compare de la sorte, par exemple frozenset
:
>>> list(unique_everseen(l, key=lambda item: frozenset(item.items())))
[{'a': 123}, {'b': 123}]
Notez que vous ne devriez pas utiliser une approche simple Tuple
(sans tri) car les dictionnaires égaux n’ont pas nécessairement le même ordre (même dans Python 3.7 où ordre d'insertion - ordre non absolu - est garanti):
>>> d1 = {1: 1, 9: 9}
>>> d2 = {9: 9, 1: 1}
>>> d1 == d2
True
>>> Tuple(d1.items()) == Tuple(d2.items())
False
Et même le tri du tuple pourrait ne pas fonctionner si les clés ne sont pas triables:
>>> d3 = {1: 1, 'a': 'a'}
>>> Tuple(sorted(d3.items()))
TypeError: '<' not supported between instances of 'str' and 'int'
J'ai pensé qu'il serait peut-être utile de voir comment se compare la performance de ces approches. J'ai donc fait un petit repère. Les graphiques de référence sont des tailles de temps et de listes basées sur une liste ne contenant pas de doublons (qui a été choisie arbitrairement, l'exécution ne change pas de manière significative si j'ajoute des doublons ou beaucoup de doublons). Il s’agit d’un graphe log-log pour couvrir toute la gamme.
Les temps absolus:
Les timings relatifs à l'approche la plus rapide:
La deuxième approche de thefourtheye est la plus rapide ici. L'approche unique_everseen
Avec la fonction key
est à la deuxième place, mais c'est l'approche la plus rapide qui préserve l'ordre. Les autres approches de jcollado et thefourtheye sont presque aussi rapides. L’approche utilisant unique_everseen
Sans clé et les solutions de Emmanuel et Scorpil sont très lentes pour les listes plus longues et se comportent bien pire O(n*n)
à la place de O(n)
. stpk L approche avec json
n est pas O(n*n)
mais elle est beaucoup plus lente que les approches similaires O(n)
.
Le code pour reproduire les repères:
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)
Pour être complet, voici le calendrier pour une liste ne contenant que des doublons:
# 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)}
Les timings ne changent pas de manière significative sauf pour unique_everseen
Sans la fonction key
, qui dans ce cas est la solution la plus rapide. Cependant, ce n'est que le meilleur des cas (donc non représentatif) pour cette fonction avec des valeurs incontrôlables, car son exécution dépend de la quantité de valeurs uniques dans la liste: O(n*m)
qui, dans ce cas, n'est que 1 et s'exécute O(n)
.
Disclaimer: Je suis l'auteur de iteration_utilities
.
Si vous utilisez Pandas dans votre flux de travail, une option consiste à alimenter une liste de dictionnaires directement dans le fichier pd.DataFrame
constructeur. Ensuite, utilisez drop_duplicates
et to_dict
méthodes pour le résultat requis.
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}]
Pas une réponse universelle , mais si votre liste se trouve être triée par une clé, comme ceci:
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}]
alors la solution est aussi simple que:
import itertools
result = [a[0] for a in itertools.groupby(l)]
Résultat:
[{'a': {'b': 31}, 't': 1},
{'a': {'b': 145}, 't': 2},
{'a': {'b': 25231}, 't': 2},
{'a': {'b': 112}, 't': 3}]
Fonctionne avec des dictionnaires imbriqués et préserve (évidemment) l'ordre.
Vous pouvez utiliser un jeu, mais vous devez transformer les dicts en un type hashable.
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)
Unique maintenant égal
set([(('a', 3222), ('b', 1234)), (('a', 123), ('b', 1234))])
Pour récupérer les dicts:
[dict(x) for x in unique]