Python - Lista de dicionários únicos

158

Digamos que eu tenho uma lista de dicionários:

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

e preciso obter uma lista de dicionários exclusivos (removendo as duplicatas):

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

Alguém pode me ajudar com a maneira mais eficiente de conseguir isso em Python?

Limaaf
fonte
5
Qual a extensão desses dicionários? Você precisa de verificação de atributo individual para determinar duplicatas ou a verificação de um único valor nelas é suficiente?
Gddc
Esses ditados têm 8 pares chave: valor e a lista tem 200 dict. Eles realmente têm um ID e é seguro remover o ditado da lista se o valor do ID encontrado for duplicado.
Limaaf 18/06/12
o forzenset é uma opção eficaz. set(frozenset(i.items()) for i in list)
Abhijeet

Respostas:

238

Então faça um ditado temporário com a chave sendo id. Isso filtra as duplicatas. O values()ditado será a lista

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

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

Em 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
fonte
@ John La Rooy - como é possível usar o mesmo para remover dicionários de uma lista com base em vários atributos, tentei isso, mas parece não funcionar> {v ['flight'] ['lon'] ['lat']: v v in stream} .values ​​()
Jorge Vidinha
1
@JorgeVidinha supondo que cada um possa ser convertido em str (ou unicode), tente o seguinte: {str(v['flight'])+':'+str(v['lon'])+','+str(v['lat']): v for v in stream}.values()Isso cria uma chave exclusiva com base em seus valores. Como'MH370:-21.474370,86.325589'
whunterknight
4
@JorgeVidinha, você pode usar uma tupla como chave do dicionário{(v['flight'], v['lon'], v['lat']): v for v in stream}.values()
John La Rooy
note que isso pode alterar a ordem dos dicionários na lista! usar OrderedDicta partir collections list(OrderedDict((v['id'], v) for v in L).values()) ou classificar a lista resultante se isso funciona melhor para você
gevra
Se você precisar de todos os valores considerados e não apenas o ID, poderá usar list({str(i):i for i in L}.values())Aqui, usamos str (i) para criar uma string exclusiva que representa o dicionário que é usado para filtrar as duplicatas.
DelboyJay
79

A maneira usual de encontrar apenas os elementos comuns em um conjunto é usar a setclasse do Python . Basta adicionar todos os elementos ao conjunto, depois converter o conjunto para a liste, uma vez que as duplicatas desapareceram.

O problema, é claro, é que a set()pode conter apenas entradas laváveis ​​e a dictnão é lavável.

Se eu tivesse esse problema, minha solução seria converter cada dictuma em uma string que represente o dict, adicione todas as strings a e set()depois leia os valores da string como list()ae converta novamente em dict.

Uma boa representação de um dictformato de sequência é o formato JSON. E o Python possui um módulo interno para JSON (chamado, é jsonclaro).

O problema restante é que os elementos em a dictnão são ordenados e, quando o Python converte a dictstring em JSON, você pode obter duas strings JSON que representam dicionários equivalentes, mas não são strings idênticas. A solução fácil é passar o argumento sort_keys=Truequando você ligar json.dumps().

EDIT: Esta solução estava assumindo que um dado dictpoderia ter qualquer parte diferente. Se pudermos assumir que todos dictcom o mesmo "id"valor corresponderão ao outro dictcom o mesmo "id"valor, isso será um exagero; A solução do @ gnibbler seria mais rápida e fácil.

EDIT: Agora, há um comentário de André Lima dizendo explicitamente que, se o ID for uma duplicata, é seguro assumir que o todo dicté uma duplicata. Portanto, essa resposta é um exagero e eu recomendo a resposta do @ gnibbler.

steveha
fonte
Obrigado pela ajuda steveha. A sua resposta realmente me deu algum conhecimento eu não tinha, já que eu só comecei com Python =)
Limaaf
1
Enquanto um exagero dado o ID nesse caso específico, essa ainda é uma excelente resposta!
Josh Werts
8
Isso me ajuda, pois meu dicionário não possui uma chave e é identificado exclusivamente por todas as suas entradas. Obrigado!
ericso
Essa solução funciona na maioria das vezes, mas pode haver problemas de desempenho com a expansão, mas acho que o autor sabe disso e, portanto, recomenda a solução com "id". Preocupações com o desempenho: Esta solução usa serialização para cadeia e desserialização ... serializar / desserializar é um cálculo caro e geralmente não aumenta bem (o número de itens é n> 1e6 ou cada dicionário contém> 1e6 itens ou ambos) ou se você tiver para executar isso muitas vezes> 1e6 ou frequentemente.
Trevor Boyd Smith
Por outro lado, esta solução ilustra um ótimo exemplo canônico de por que você desejaria projetar sua solução ... ou seja, se você tem um ID exclusivo ... você pode acessar os dados com eficiência ... se você é preguiçoso e não tiver um ID, seu acesso a dados será mais caro.
Trevor Boyd Smith
21

Caso os dicionários sejam identificados exclusivamente por todos os itens (o ID não está disponível), você poderá usar a resposta usando JSON. A seguir, é uma alternativa que não usa JSON e funcionará desde que todos os valores do dicionário sejam imutáveis

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

Você pode usar a biblioteca numpy (funciona apenas para Python2.x):

   import numpy as np 

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

Para que ele funcione com o Python 3.x (e versões recentes do numpy), você precisa converter uma matriz de dicts em uma matriz numpy de seqüências de caracteres, por exemplo

list_of_unique_dicts=list(np.unique(np.array(list_of_dicts).astype(str)))
bolha
fonte
13
Obtenha o erro TypeError: unorderable types: dict() > dict()ao fazer isso no Python 3.5.
Guillochon
16

Aqui está uma solução razoavelmente compacta, embora eu suspeito que não seja particularmente eficiente (para dizer o mínimo):

>>> 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.
fonte
3
Coloque a map()chamada list()no Python 3 para recuperar uma lista, caso contrário, é um mapobjeto.
DMN
um benefício adicional desta abordagem em python 3.6+ é que a lista de ordenação é preservada
jnnnnn
7

Como o idé suficiente para detectar duplicatas e o idé lavável: execute-o através de um dicionário que tenha idcomo chave. O valor para cada chave é o dicionário original.

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

No Python 3, values()não retorna uma lista; você precisará envolver todo o lado direito dessa expressão list()e escrever a carne da expressão mais economicamente como uma compreensão de ditado:

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

Observe que o resultado provavelmente não estará na mesma ordem que o original. Se isso é um requisito, você pode usar a em Collections.OrderedDictvez de a dict.

Como um aparte, pode fazer bastante sentido manter apenas os dados em um dicionário que use a idchave como para começar.

kindall
fonte
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)

saídas:

[{'idade': 34, 'id': 1, 'nome': 'john'}, {'idade': 30, 'id': 2, 'nome': 'hanna'}]

Yusuf X
fonte
No mesmo exemplo. como posso obter os dictos contendo apenas os IDs semelhantes?
user8162
@ user8162, como você gostaria que a saída fosse?
Yusuf X
Às vezes, terei o mesmo ID, mas com idade diferente. então a saída deve ser [{'age': [34, 40], 'id': 1, 'name': ['john', Peter]}]. Em resumo, se os IDs forem iguais, combine o conteúdo de outras pessoas para uma lista, como mencionei aqui. Desde já, obrigado.
precisa saber é o seguinte
1
b = {x ['id']: [y para y em a se y ['id'] == x ['id']] para x em a} é uma maneira de agrupá-los.
Yusuf X
4

Expandindo a resposta de John La Rooy ( Python - Lista de dicionários únicos ), tornando-a um pouco mais flexível:

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

Função de chamada:

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

Nós podemos fazer com 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 um pouco diferente da resposta de aceitação.

drop_duplicates irá verificar todas as colunas nos pandas, se todas forem iguais, a linha será descartada.

Por exemplo :

Se mudarmos o segundo dictnome de john para 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
fonte
2

No python 3.6+ (o que eu testei), basta usar:

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)

Explicação: estamos mapeando json.dumpspara codificar os dicionários como objetos json, que são imutáveis. setpode então ser usado para produzir um iterável de imutáveis exclusivos . Finalmente, convertemos de volta para a nossa representação de dicionário usando json.loads. Observe que, inicialmente, é preciso classificar por chaves para organizar os dicionários de uma forma única. Isso é válido para o Python 3.6+, pois os dicionários são ordenados por padrão.

VanillaSpinIce
fonte
1
Lembre-se de classificar as chaves antes de exportar para JSON. Você também não precisa converter para listantes de fazer set.
19419 Nathan
2

Resumi meus favoritos para experimentar:

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

Uma solução rápida e suja é apenas gerar uma nova lista.

sortedlist = []

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

Não sei se você deseja apenas que o id de seus dictos na lista seja exclusivo, mas se o objetivo é ter um conjunto de dictos em que a unicidade esteja nos valores de todas as chaves. em sua compreensão:

>>> 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 ajude você ou outra pessoa que tenha a preocupação ....

nixmind
fonte
1

Há muitas respostas aqui, então deixe-me adicionar outra:

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

Opção bastante simples:

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

Bem, todas as respostas mencionadas aqui são boas, mas em algumas respostas pode-se enfrentar um erro se os itens do dicionário tiverem lista ou dicionário aninhado, então proponho uma resposta simples

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

Heres uma implementação com pouca sobrecarga de memória ao custo de não ser tão compacta quanto o 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

resultado:

[{'age': 30, 'id': 2, 'name': 'hanna'}, {'age': 34, 'id': 1, 'name': 'john'}]
Samy Vilar
fonte
1
Você precisa testar isso um pouco mais. Modificando a lista enquanto estiver a iteração sobre ele pode nem sempre funcionam como você espera
John La Rooy
@gnibbler ponto muito bom! Vou excluir a resposta e testá-la mais detalhadamente.
Samy Vilar
Parece melhor. Você pode usar um conjunto para acompanhar os IDs em vez do dict. Considere iniciar o indexat len(values)e contando para trás, o que significa que você sempre pode diminuir indexse você está delou não. por exemplofor index in reversed(range(len(values))):
John La Rooy
@gnibbler interessante, os conjuntos têm uma aparência quase constante como dicionários?
Samy Vilar
-4

Esta é a solução que encontrei:

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

Basicamente, você verifica se o ID está presente na lista; se estiver, exclua o dicionário; caso contrário, anexe o ID à lista.

tabchas
fonte
Eu usaria um conjunto em vez de listar para usedID. É uma rápida pesquisa, e mais legível
happydave
Sim eu não sabia sobre conjuntos ... mas estou aprendendo ... Eu estava apenas olhando para resposta @gnibbler ...
tabchas
1
Você precisa testar isso um pouco mais. Modificando a lista enquanto estiver a iteração sobre ele pode nem sempre funcionam como você espera
John La Rooy
Sim, eu não entendo por que não funciona ... Alguma idéia do que estou fazendo de errado?
tabchas
Não, eu peguei o problema ... é só que eu não entendo por que está dando esse problema ... você sabe?
Tab