Удалить дубликаты в списке в Python - PullRequest
102 голосов
/ 24 февраля 2012

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

Для этого списка: [{'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}]

Ответы [ 9 ]

177 голосов
/ 24 февраля 2012

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

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

Стратегия заключается в преобразовании списка словарей в список кортежей, в которых кортежи содержат элементы словаря. Поскольку кортежи можно хэшировать, вы можете удалить дубликаты, используя set (здесь используется set compceptionsion , более старая альтернатива 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(), как он предлагает.

38 голосов
/ 24 февраля 2012

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

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

14 голосов
/ 24 февраля 2012

Иногда петли старого стиля все еще полезны.Этот код немного длиннее кода jcollado, но очень легко читается:

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])
13 голосов
/ 02 августа 2016

Другие ответы не будут работать, если вы работаете с вложенными словарями, такими как десериализованные объекты 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]
9 голосов
/ 29 апреля 2014

Если вы хотите сохранить ордер, тогда вы можете сделать

from collections import OrderedDict
print OrderedDict((frozenset(item.items()),item) for item in data).values()
# [{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}]

Если заказ не имеет значения, тогда вы можете сделать

print {frozenset(item.items()):item for item in data}.values()
# [{'a': 3222, 'b': 1234}, {'a': 123, 'b': 1234}]
5 голосов
/ 01 августа 2018

Если вы используете Pandas в своем рабочем процессе, одним из вариантов является подача списка словарей непосредственно в конструктор pd.DataFrame.Затем используйте drop_duplicates и to_dict методы для требуемого результата.

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}]
5 голосов
/ 17 июля 2018

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

Тест

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

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

enter image description here

Время относительно самого быстрогозаход на посадку:

enter image description here

Второй подход с thefourtheye здесь самый быстрый.Подход unique_everseen с функцией key находится на втором месте, однако это самый быстрый подход, который сохраняет порядок.Другие подходы из jcollado и thefourtheye почти такие же быстрые.Подход с использованием unique_everseen без ключа и решений от Эммануила и Scorpil очень медленен для длинных списков и ведет себя намного хуже 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)}

enter image description here

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


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

1 голос
/ 14 июня 2018

Не универсальный ответ , но если ваш список будет отсортирован по некоторому ключу, например так:

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

тогда решение так же просто, как:

import itertools
result = [a[0] for a in itertools.groupby(l)]

Результат:

[{'a': {'b': 31}, 't': 1},
{'a': {'b': 145}, 't': 2},
{'a': {'b': 25231}, 't': 2},
{'a': {'b': 112}, 't': 3}]

Работает с вложенными словарями и (очевидно) сохраняет порядок.

0 голосов
/ 24 февраля 2012

Вы можете использовать набор, но вам нужно превратить дикты в хэш-тип.

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)

Уникальный теперь равен

set([(('a', 3222), ('b', 1234)), (('a', 123), ('b', 1234))])

Чтобы вернуть назад:

[dict(x) for x in unique]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...