Supprimer les dicts dupliqués dans la liste en Python

j'ai une liste de dicts, et je voudrais supprimer les dicts identique de paires clé-valeur.

pour cette liste: [{'a': 123}, {'b': 123}, {'a': 123}]

j'aimerais vous retourner ceci: [{'a': 123}, {'b': 123}]

autre exemple:

pour cette liste: [{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}, {'a': 123, 'b': 1234}]

j'aimerais vous retourner ceci: [{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}]

86
demandé sur thefourtheye 2012-02-24 11:46:41

9 réponses

essayez ceci:

[dict(t) for t in {tuple(d.items()) for d in l}]

La stratégie est de convertir la liste des dictionnaires à une liste de tuples où les tuples contiennent les éléments du dictionnaire. Puisque les tuples peuvent être hachés, vous pouvez supprimer les doublons en utilisant set (en utilisant un set comprehension ici, l'alternative plus ancienne de python serait set(tuple(d.items()) for d in l) ) et, après cela, recréer les dictionnaires à partir de tuples avec dict .

où:

  • l est la liste originale
  • d est l'un des dictionnaires de la liste
  • t est l'un des tuples créés à partir d'un dictionnaire

Edit: si vous voulez préserver la commande, le One-liner ci-dessus ne fonctionnera pas puisque set ne le fera pas. Cependant, avec quelques lignes de code, vous pouvez le faire aussi:

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

Note: Comme le souligne @alexis, il pourrait arriver que deux dictionnaires ayant les mêmes clés et valeurs ne donnent pas le même tuple. Cela pourrait se produire s'ils passent par une histoire d'Ajout/Suppression de clés différente. Si c'est le cas pour votre problème, alors envisager de trier d.items() comme il suggère.

149
répondu jcollado 2018-07-17 15:26:05

un Autre one-liner basées sur des interprétations de la 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 la comparaison dict , nous ne conservons que les éléments qui ne sont pas dans le reste de la liste initiale (cette notion n'est accessible que par l'index n , d'où l'utilisation de enumerate ).

29
répondu Emmanuel 2012-02-24 09:10:56

parfois, les boucles anciennes 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])
12
répondu Scorpil 2017-12-11 10:50:18

D'autres réponses ne fonctionneraient pas si vous utilisez 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]
9
répondu stpk 2016-08-02 13:52:24

Si vous voulez conserver 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'importe pas, alors vous pouvez faire

print {frozenset(item.items()):item for item in data}.values()
# [{'a': 3222, 'b': 1234}, {'a': 123, 'b': 1234}]
8
répondu thefourtheye 2014-04-29 07:52:59

Pas de réponse universelle , mais si votre liste est triés par certains grands, 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 (évidemment) préserve l'ordre.

1
répondu Highstaker 2018-06-14 09:22:55

vous pouvez utiliser un ensemble, mais vous devez transformer les dicts en un type hachable.

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

set([(('a', 3222), ('b', 1234)), (('a', 123), ('b', 1234))])

pour récupérer les dicts:

[dict(x) for x in unique]
0
répondu Matimus 2012-02-24 08:03:31

si l'utilisation d'un paquet tiers est acceptable, Vous pouvez 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 les éléments indéchiffrables comme les dictionnaires en retombant sur un algorithme plus lent ( O(n*m)n sont les éléments dans la liste d'origine et m les éléments uniques dans la liste d'origine au lieu de O(n) ). Dans le cas où les deux clés et les valeurs sont hashable vous pouvez utiliser l'argument key de cette fonction pour créer des éléments hashables pour le "uniqueness-test" (de sorte qu'il fonctionne dans O(n) ).

dans le cas d'un dictionnaire (qui compare indépendamment de l'ordre), vous devez l'associer à une autre structure de données qui compare comme cela, 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) parce que dictionnaires égaux n'ont pas nécessairement le même ordre (même en python 3.7 où ordre d'insertion - pas ordre 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 trier le tuple pourrait ne pas fonctionner si les clés ne sont pas sortables:

>>> d3 = {1: 1, 'a': 'a'}
>>> tuple(sorted(d3.items()))
TypeError: '<' not supported between instances of 'str' and 'int'

de Référence", 1519430920"

j'ai pensé qu'il pourrait être utile de voir comment la performance de ces approches se compare, donc j'ai fait un petit benchmark. Les graphiques de référence sont Temps vs. list-size basé sur une liste ne contenant pas de doublons (qui a été choisi arbitrairement, l'exécution ne change pas de manière significative si j'ajoute quelques ou beaucoup de doublons). C'est un tracé en logarithme, donc la gamme complète est couverte.

L'absolu":

enter image description here

Les timings par rapport à l'approche plus rapide:

enter image description here

la deuxième approche de theourtheye 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 theourtheye sont presque aussi rapides. L'approche utilisant unique_everseen sans clé et les solutions de Emmanuel et Scorpil sont très lents pour plus de listes et de se comporter bien pire O(n*n) au lieu de O(n) . stpk s approche avec json n'est pas O(n*n) mais il est beaucoup plus lent que les approches similaires O(n) .

Le code de reproduire la référence:

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)

par souci d'exhaustivité, 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)}

enter image description here

les minuteries ne changent pas de manière significative sauf pour unique_everseen sans fonction key , qui dans ce cas est la solution la plus rapide. Cependant, c'est juste le meilleur cas (donc pas représentatif) pour cette fonction avec des valeurs indéchiffrables parce que son exécution dépend de la quantité de valeurs uniques dans la liste: O(n*m) qui dans ce cas est juste 1 et donc il s'exécute dans O(n) .


clause de non-responsabilité: je suis l'auteur de iteration_utilities .

0
répondu MSeifert 2018-07-17 19:43:56

si vous utilisez Pandas dans votre workflow, une option consiste à alimenter une liste de dictionnaires directement au constructeur pd.DataFrame . Utilisez ensuite les méthodes drop_duplicates et to_dict pour obtenir 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}]
0
répondu jpp 2018-08-01 13:34:58