Вопрос-Ответ

Remove duplicate dict in list in Python

Удалить дублирующийся dict в списке в Python

У меня есть список dict, и я хотел бы удалить dict с идентичными парами ключей и значений.

Для этого списка: [{'a': 123}, {'b': 123}, {'a': 123}]

Я хотел бы вернуть это: [{'a': 123}, {'b': 123}]

Другой пример:

Для этого списка: [{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}, {'a': 123, 'b': 1234}]

Я хотел бы вернуть это: [{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}]

Переведено автоматически
Ответ 1

Попробуйте это:

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

Стратегия заключается в преобразовании списка словарей в список кортежей, где кортежи содержат элементы словаря. Поскольку кортежи могут быть хэшированы, вы можете удалить дубликаты с помощью set (используя установленное понимание здесь, более старой альтернативой python было бы set(tuple(d.items()) for d in l)) и, после этого, заново создать словари из кортежей с помощью dict.

где:


  • l является исходным списком

  • d является одним из словарей в списке

  • t является одним из кортежей, созданных из словаря

Редактировать: если вы хотите сохранить порядок, приведенная выше однострочная строка не будет работать, поскольку set этого не сделает. Однако с помощью нескольких строк кода вы также можете это сделать:

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

Пример вывода:

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

Примечание: Как указал @alexis, может случиться так, что два словаря с одинаковыми ключами и значениями не приведут к одному и тому же кортежу. Это может произойти, если они проходят через разную историю добавления / удаления ключей. Если это относится к вашей проблеме, то рассмотрите сортировку d.items(), как он предлагает.

Ответ 2

Еще один однострочник, основанный на понимании списка:

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

Здесь, поскольку мы можем использовать dict сравнение, мы сохраняем только элементы, которых нет в остальной части исходного списка (это понятие доступно только через индекс n, отсюда использование enumerate).

Ответ 3

Если использование стороннего пакета было бы нормально, вы могли бы использовать 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}]

Он сохраняет порядок исходного списка, и ut также может обрабатывать нехешируемые элементы, такие как словари, используя более медленный алгоритм (O(n*m) где n элементы в исходном списке и m уникальные элементы в исходном списке вместо O(n)). В случае, если оба ключа и значения являются хэшируемыми, вы можете использовать key аргумент этой функции для создания хэшируемых элементов для "теста уникальности" (чтобы он работал в O(n)).

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

>>> list(unique_everseen(l, key=lambda item: frozenset(item.items())))
[{'a': 123}, {'b': 123}]

Обратите внимание, что вам не следует использовать простой tuple подход (без сортировки), потому что одинаковые словари не обязательно имеют одинаковый порядок (даже в Python 3.7, где гарантирован порядок вставки, а не абсолютный порядок):

>>> d1 = {1: 1, 9: 9}
>>> d2 = {9: 9, 1: 1}
>>> d1 == d2
True
>>> tuple(d1.items()) == tuple(d2.items())
False

И даже сортировка кортежа может не сработать, если ключи не поддаются сортировке:

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

Бенчмарк

Я подумал, что было бы полезно посмотреть, как сравнивается производительность этих подходов, поэтому я провел небольшой тест. Контрольные графики зависимости времени от размера списка основаны на списке, не содержащем дубликатов (который был выбран произвольно, среда выполнения существенно не изменится, если я добавлю несколько или много дубликатов). Это логарифмический график, поэтому охватывается весь диапазон.

Абсолютное время:

введите описание изображения здесь

Тайминги относительно самого быстрого подхода:

введите описание изображения здесь

Второй подход из thefourtheye здесь самый быстрый. unique_everseen Подход с key функцией находится на втором месте, однако это самый быстрый подход, который сохраняет порядок. Другие подходы от jcollado и thefourtheye почти такие же быстрые. Подход, использующий unique_everseen without key, и решения от Эммануэля и Скорпила, очень медленны для более длинных списков и работают намного хуже O(n*n) вместо O(n). Подход stpk с jsonнет O(n*n), но он намного медленнее, чем аналогичные O(n) подходы.

Код для воспроизведения тестов:

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)

Для полноты картины приведено время для списка, содержащего только дубликаты:

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

введите описание изображения здесь

Тайминги существенно не меняются, за исключением функции unique_everseen without key, которая в данном случае является самым быстрым решением. Однако это всего лишь лучший случай (поэтому не репрезентативный) для этой функции с нехешируемыми значениями, потому что время выполнения зависит от количества уникальных значений в списке: O(n*m) который в данном случае равен всего 1 и, следовательно, выполняется в O(n).


Отказ от ответственности: Я автор iteration_utilities.

Ответ 4

Другие ответы не будут работать, если вы работаете с вложенными словарями, такими как десериализованные объекты JSON. В этом случае вы могли бы использовать:

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]
2023-08-03 21:47 python list dictionary