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?


5
Наскільки обширні ці словники? Чи потрібна індивідуальна перевірка атрибутів для визначення дублікатів чи достатня перевірка одного значення в них?
gddc

Ці дикти отримали 8 ключів: пари значень, а список отримав 200 диктів. Насправді вони отримали ідентифікатор, і для мене безпечно видалити dict зі списку, якщо знайдене значення дубліката є дублікатом.
Лімааф


forzenset - ефективний варіант. set(frozenset(i.items()) for i in list)
Abhijeet

Відповіді:


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 ['полет'] ['lon'] ['lat']: v for 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'
whunterknight

4
@JorgeVidinha, ви можете використовувати кортеж як ключ словника{(v['flight'], v['lon'], v['lat']): v for v in stream}.values()
Джон Ла Рой

зауважте, що це може змінити порядок словників у списку! використовувати 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, а бам дублікатів вже не буде.

Проблема, звичайно, полягає в тому, що set()може містити лише хешируемые записи, а а dict- не хешируемый.

Якби у мене була ця проблема, моїм рішенням було б перетворити кожну dictв рядок, що представляє собою dict, а потім додати всі рядки до set()потім прочитаних значень рядків у вигляді list()і перетворити назад у dict.

Хорошим представленням dictрядкової форми є формат JSON. І Python має вбудований модуль для JSON (називається jsonзвичайно).

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

EDIT: Це рішення передбачало, що дана dictможе мати будь-яку частину. Якщо ми можемо припустити, що кожен dictз однаковим "id"значенням буде відповідати один одному dictз однаковим "id"значенням, то це надмірно; @ рішення gnibbler було б швидше і простіше.

EDIT: Зараз є коментар від Андре Ліма, який прямо говорить про те, що якщо ідентифікатор є дублікатом, можна припустити, що ціле dict- це дублікат. Тож ця відповідь є надмірною, і я рекомендую відповідь @ gnibbler.


Дякую за допомогу steveha. Ваша відповідь фактично дала мені певні знання, яких я не мав, оскільки я тільки почав з Python =)
Limaaf

1
Незважаючи на те, що в цьому конкретному випадку ідентифікатор надмірний, це все ще відмінна відповідь!
Джош Вертс

8
Це допомагає мені, оскільки в моєму словнику немає ключа, а його однозначно ідентифікують усі його записи. Дякую!
ericso

Це рішення працює більшу частину часу, але можуть виникнути проблеми з масштабуванням, але автор, я думаю, це знає, і тому рекомендує рішення з "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), вам потрібно перетворити масив диктовок у 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об'єкт.
dmn

додатковою перевагою такого підходу в 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ключ.


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


У тому ж прикладі. як я можу отримати дикти, що містять лише подібні ідентифікатори?
користувач8162

@ user8162, як би ви хотіли виглядати вихідні дані?
Юсуф X

Іноді у мене буде такий самий посвідчення особи, але різного віку. таким чином виведення має бути [{'age': [34, 40], 'id': 1, 'name': ['john', Peter]}]. Якщо коротко, якщо ідентифікатори однакові, то комбінуйте вміст інших до списку, як я вже згадував тут. Заздалегідь спасибі.
user8162

1
b = {x ['id']: [y для y in a, якщо y ['id'] == x ['id']] для x in a} - це один із способів згрупувати їх.
Юсуф X

4

Розширення на відповідь Джона Ла Роя ( 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ім’я з john на 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'}]

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+, оскільки словники впорядковані за замовчуванням.


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

2

Я узагальнив свої улюблені, щоб спробувати:

https://repl.it/@SmaMa/Python-List-of-unique-dic Словники

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

1

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

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

Сподіваюсь, це допоможе вам чи іншій особі, яка має занепокоєння ....


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)

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

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. Розглянемо запуском indexв 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

В основному ви перевіряєте, чи ідентифікатор присутній у списку, якщо він є, ви видаляєте словник, якщо ні, додаєте ідентифікатор до списку


Я б використовував набір, а не список для usedID. Це швидший пошук і більш читабельний
happydave

Так, я не знав про набори ... але я вчуся ... Я просто дивився на відповідь @gnibbler ...
tabchas

1
Вам потрібно перевірити це трохи більше. Змінення списку під час ітерації над ним може не завжди працювати так, як ви очікували
Джон Ла Рой

Так, я не розумію, чому це не працює ... Будь-які ідеї, що я роблю неправильно?
табчі

Ні, я не впіймав проблему ... це просто те, що я не розумію, чому це дає цю проблему ... ти знаєш?
табчі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.