web-dev-qa-db-fra.com

Supprimer les doublons dans la liste de Python

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

106
Brenden

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'origine
  • d est l'un des dictionnaires de la liste
  • t est l'un des n-uplets créés à partir d'un dictionnaire

Edit: 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.

183
jcollado

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

38
Emmanuel

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])
14
Scorpil

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]
13
stpk

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

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

Référence

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:

enter image description here

Les timings relatifs à l'approche la plus rapide:

enter image description here

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

enter image description here

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.

8
MSeifert

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

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.

1
Highstaker

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]
0
Matimus