Python - список уникальных словарей

158

Допустим, у меня есть список словарей:

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

и мне нужно получить список уникальных словарей (удалив дубликаты):

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

Может ли кто-нибудь помочь мне с наиболее эффективным способом достижения этого в Python?

Limaaf
источник
5
Насколько обширны эти словари? Вам нужна индивидуальная проверка атрибутов для определения дубликатов, или достаточно проверки одного значения в них?
GDCD
Эти дикты получили 8 пар ключ: значение, а список получил 200 диктов. На самом деле они получили идентификатор, и я могу удалить диктат из списка, если найденное значение идентификатора является дубликатом.
Лимааф
Forzenset является эффективным вариантом. set(frozenset(i.items()) for i in list)
Абхиджит

Ответы:

238

Так что сделайте временный дикт с ключом id. Это отфильтровывает дубликаты. values()В Словаре будет список

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

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

В 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 - как можно использовать одно и то же для удаления словарей из списка, основанного на нескольких атрибутах, пробовал это, но, похоже, не работает> {v ['flight'] ['lon'] ['lat']: v для v в потоке} .values ​​()
Хорхе Видинья
1
@JorgeVidinha, предполагая, что каждый может быть приведен к str (или unicode), попробуйте это: {str(v['flight'])+':'+str(v['lon'])+','+str(v['lat']): v for v in stream}.values()Это просто создает уникальный ключ на основе ваших значений. 'MH370:-21.474370,86.325589'
Мне
4
@JorgeVidinha, вы можете использовать кортеж в качестве словарного ключа{(v['flight'], v['lon'], v['lat']): v for v in stream}.values()
John La Rooy
обратите внимание, что это может изменить порядок словарей в списке! использовать OrderedDictиз collections list(OrderedDict((v['id'], v) for v in L).values()) или сортировки результирующего списка , если это работает лучше для вас
gevra
Если вам нужны все значения, а не только идентификатор, который вы можете использовать. list({str(i):i for i in L}.values())Здесь мы используем str (i) для создания уникальной строки, представляющей словарь, который используется для фильтрации дубликатов.
DelboyJay
79

Обычный способ найти только общие элементы в наборе - использовать setкласс Python . Просто добавьте все элементы в набор, затем преобразуйте набор в a list, и, наконец, дубликаты исчезнут.

Проблема, конечно, заключается в том, что a set()может содержать только хэшируемые записи, а a dictне хэшируемый.

Если бы у меня возникла эта проблема, мое решение dictсостояло бы в том, чтобы преобразовать каждую в строку, которая представляет собой dict, затем добавить все строки в a, set()затем прочитать значения строки как a list()и преобразовать обратно в dict.

Хорошее представление dictв виде строки - это формат JSON. А в Python есть встроенный модуль для JSON (он называется json).

Оставшаяся проблема заключается в том, что элементы в a dictне упорядочены, и когда Python преобразует их dictв строку JSON, вы можете получить две строки JSON, которые представляют собой эквивалентные словари, но не являются идентичными строками. Простое решение - передать аргумент sort_keys=Trueпри вызове json.dumps().

РЕДАКТИРОВАТЬ: Это решение предполагало, что данный dictможет иметь любую часть отличается. Если мы можем предположить, что каждый dictс одинаковым "id"значением будет совпадать dictс другим с одинаковым "id"значением, то это излишне; Решение @ gnibbler будет быстрее и проще.

РЕДАКТИРОВАТЬ: Теперь есть комментарий от Андре Лима, явно говоря, что если идентификатор является дубликатом, можно с уверенностью предположить, что все dictявляется дубликатом. Так что этот ответ излишний, и я рекомендую ответ @ gnibbler.

steveha
источник
Спасибо за помощь стевеха. Ваш ответ фактически дал мне некоторые знания, которых у меня не было, так как я только начал с Python =)
Limaaf
1
Несмотря на то, что в данном конкретном случае было получено избыточное количество идентификаторов, это все же отличный ответ!
Джош Вертс
8
Это помогает мне, так как мой словарь не имеет ключа, а только однозначно идентифицируется всеми его записями. Спасибо!
Эриксо
Это решение работает большую часть времени, но могут возникнуть проблемы с производительностью при увеличении, но автор, я думаю, знает об этом и поэтому рекомендует решение с «id». Проблемы производительности: это решение использует сериализацию для строки, а затем десериализации ... сериализация / десериализация является дорогостоящим вычислением и обычно плохо масштабируется (количество элементов равно n> 1e6, или каждый словарь содержит> 1e6 элементов или оба), или если у вас есть выполнить это много раз> 1e6 или часто.
Тревор Бойд Смит
Коротко говоря, это решение иллюстрирует отличный канонический пример того, почему вы хотели бы разработать свое решение ... т.е. если у вас есть уникальный идентификатор ... тогда вы можете эффективно получить доступ к данным ... если вы ленивы и не иметь идентификатора, тогда ваш доступ к данным дороже.
Тревор Бойд Смит
21

Если словари однозначно идентифицируются по всем элементам (идентификатор недоступен), вы можете использовать ответ, используя JSON. Следующее является альтернативой, которая не использует JSON и будет работать до тех пор, пока все значения словаря неизменны

[dict(s) for s in set(frozenset(d.items()) for d in L)]
Сина
источник
19

Вы можете использовать библиотеку numpy (работает только для Python2.x):

   import numpy as np 

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

Чтобы это работало с Python 3.x (и последними версиями numpy), вам нужно преобразовать массив dicts в numpy массив строк, например

list_of_unique_dicts=list(np.unique(np.array(list_of_dicts).astype(str)))
пузырь
источник
13
Получите ошибку TypeError: unorderable types: dict() > dict()при выполнении этого в Python 3.5.
Гильошон
16

Вот довольно компактное решение, хотя я подозреваю, что оно не особенно эффективно (мягко говоря):

>>> 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'}]
Грег Э.
источник
3
Окружите map()вызов list()в Python 3, чтобы получить список обратно, иначе это mapобъект.
д.м.н.
дополнительное преимущество этого подхода в Python 3.6+ заключается в том, что порядок списков сохраняется
jnnnnn
7

Так как idдля обнаружения дубликатов достаточно, а для idхэширования - запустите их через словарь, в котором idключом является ключ. Значением для каждого ключа является исходный словарь.

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

В Python 3 values()не возвращает список; вам нужно будет обернуть всю правую часть этого выражения list(), и вы можете написать экономически более выразительное выражение этого выражения для более глубокого понимания:

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

Обратите внимание, что результат, скорее всего, будет не в том же порядке, что и оригинал. Если это требование, вы можете использовать Collections.OrderedDictвместо dict.

Кроме того, может иметь смысл сохранять данные в словаре, который idдля начала использует ключ as.

Kindall
источник
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)

выходы:

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

Юсуф Икс
источник
В том же примере. как я могу получить дикты, содержащие только одинаковые идентификаторы?
user8162
@ user8162, как бы вы хотели, чтобы вывод выглядел?
Юсуф Икс
Иногда у меня будет одинаковое удостоверение личности, но разного возраста. поэтому выведите: [{'age': [34, 40], 'id': 1, 'name': ['john', Peter]}]. Короче говоря, если идентификаторы совпадают, то объедините содержимое других в список, как я упоминал здесь. Заранее спасибо.
user8162
1
b = {x ['id']: [y для y в a, если y ['id'] == x ['id']] для x в a} - это один из способов их группировки.
Юсуф Икс
4

Расширяя ответ John La Rooy ( Python - Список уникальных словарей ), делая его немного более гибким:

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

Функция вызова:

sorted_list_of_dicts = dedup_dict_list(
    unsorted_list_of_dicts, ['id', 'name'])
Незаконный оператор
источник
4

Мы можем сделать с 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'}]

Обратите внимание, немного отличается от принять ответ.

drop_duplicates проверит все столбцы в пандах, если все одинаковые, то строка будет удалена.

Например :

Если мы изменим 2-е dictимя с Джона на Питера

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
источник
2

В Python 3.6+ (что я тестировал) просто используйте:

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)

Объяснение: мы сопоставляем json.dumpsкодирование словарей как объектов json, которые являются неизменяемыми. setзатем может быть использован для создания итерируемой уникальной неизменяемой. Наконец, мы преобразуем обратно в наше словарное представление, используя json.loads. Обратите внимание, что изначально нужно сортировать по ключам, чтобы словари располагались в уникальной форме. Это действительно для Python 3.6+, так как словари упорядочены по умолчанию.

VanillaSpinIce
источник
1
Не забудьте отсортировать ключи перед сбросом в JSON. Вам также не нужно конвертировать в, listпрежде чем делать set.
Натан,
2

Я суммировал мои любимые, чтобы попробовать:

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)
Сма Ма
источник
1

Быстрое и грязное решение заключается в создании нового списка.

sortedlist = []

for item in listwhichneedssorting:
    if item not in sortedlist:
        sortedlist.append(item)
lyzazel
источник
1

Я не знаю, хотите ли вы, чтобы идентификатор ваших dicts только в списке был уникальным, но если цель состоит в том, чтобы иметь набор dict, в котором уникальность находится на значениях всех ключей ... вы должны использовать кортежи key следующим образом в вашем понимании:

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

Надеюсь, это поможет вам или другому человеку, имеющему проблемы ....

nixmind
источник
1

Здесь много ответов, поэтому позвольте мне добавить еще один:

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
источник
0

Довольно простой вариант:

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
источник
0

Ну, все ответы, упомянутые здесь, хороши, но в некоторых ответах можно столкнуться с ошибкой, если элементы словаря имеют вложенный список или словарь, поэтому я предлагаю простой ответ

a = [str(i) for i in a]
a = list(set(a))
a = [eval(i) for i in a]
ПРАХАР КАУШИК
источник
-1

Вот реализация с небольшим объемом памяти за счет того, что она не такая компактная, как остальные.

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

вывод:

[{'age': 30, 'id': 2, 'name': 'hanna'}, {'age': 34, 'id': 1, 'name': 'john'}]
Сами Вилар
источник
1
Вы должны проверить это немного больше. Изменение списка во время его итерации может не всегда работать так, как вы ожидаете
Джон Ла Рой
@gnibbler очень хорошая мысль! Я удалю ответ и проверю его более тщательно.
Сами Вилар
Выглядит лучше. Вы можете использовать набор для отслеживания идентификаторов вместо dict. Подумайте о том, чтобы начать indexat len(values)и считать в обратном направлении, это означает, что вы всегда можете уменьшить значение, indexнезависимо от того, есть вы delили нет. напримерfor index in reversed(range(len(values))):
Джон Ла Рой
@gnibbler интересно, наборы имеют почти постоянный вид словари?
Сами Вилар
-4

Это решение, которое я нашел:

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

В основном вы проверяете, присутствует ли идентификатор в списке, если он есть, удаляете словарь, если нет, добавляете идентификатор в список.

tabchas
источник
Я бы использовал набор, а не список для usedID. Это более быстрый поиск и более читаемый
happydave
Да, я не знал о наборах ... но я учусь ... Я просто смотрел на ответ @gnibbler ...
tabchas
1
Вы должны проверить это немного больше. Изменение списка во время его итерации может не всегда работать так, как вы ожидаете
Джон Ла Рой
Да, я не понимаю, почему это не работает ... Есть идеи, что я делаю не так?
Tabchas
Нет, я поймал проблему ... просто я не понимаю, почему это создает эту проблему ... ты знаешь?
Табчас