Якщо використання сторонніх пакетів було б добре, ви можете використовувати 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'
Орієнтир
Я вважав, що може бути корисним побачити, як порівнюється ефективність цих підходів, тому я зробив невеликий орієнтир. Графіки орієнтування - це час та розмір списку на основі списку, що не містить дублікатів (це було вибрано довільно; час виконання не зміниться суттєво, якщо я додаю кілька або багато дублікатів). Це графік журналу журналу, тому весь діапазон охоплюється.
Абсолютні часи:
Терміни відносно найшвидшого підходу:
Тут найшвидший другий підхід з боку очей . unique_everseen
Підхід з key
функцією знаходиться на другому місці, однак це найшвидший підхід , який зберігає замовлення. Інші підходи від jcollado і thefourtheye майже так само швидко. Підхід із використанням unique_everseen
без ключа та рішень Еммануїла та Скорпіля дуже повільний для більш довгих списків і поводиться набагато гірше 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
key
функції без функції, яка в цьому випадку є найшвидшим рішенням. Однак це лише найкращий випадок (настільки не репрезентативний) для цієї функції з незмінними значеннями, оскільки час її виконання залежить від кількості унікальних значень у списку: O(n*m)
що в цьому випадку становить лише 1, і, таким чином, воно працює O(n)
.
Відмова: Я автор автора iteration_utilities
.