타사 패키지를 사용해도 괜찮다면 다음을 사용할 수 있습니다 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}]
그것은 원래 목록의 순서를 유지하고 유타도 (느린 알고리즘에 다시 하락에 의해 사전 같은 unhashable 항목을 처리 할 수있는 원본 목록의 요소입니다 원래 목록 대신에 독특한 요소 ). 키와 값을 모두 해시 할 수있는 경우 해당 함수 의 인수를 사용 하여 "고유성 테스트"에 대한 해시 가능한 항목을 만들 수 있습니다 (에서 작동 ).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'
기준
이러한 접근 방식의 성능이 어떻게 비교되는지 보는 것이 도움이 될 것이라고 생각했기 때문에 작은 벤치 마크를 수행했습니다. 벤치 마크 그래프는 중복이 포함되지 않은 목록을 기반으로 한 시간 대 목록 크기입니다 (임의로 선택되었으므로 중복을 많이 추가하면 런타임이 크게 변경되지 않습니다). 로그-로그 플롯이므로 전체 범위가 포함됩니다.
절대 시간 :
가장 빠른 접근 방식과 관련된 타이밍 :
fourtheye 의 두 번째 접근 방식 이 가장 빠릅니다. 이 기능을 사용한 unique_everseen
접근 방식 key
은 두 번째이지만 주문을 유지하는 가장 빠른 접근 방식입니다. 다른에서 접근 jcollado 및 thefourtheye은 거의 빠르다. unique_everseen
키를 사용 하지 않고 Emmanuel 과 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)}
기능 이 unique_everseen
없는 경우를 제외하고 타이밍이 크게 바뀌지 않습니다 key
.이 경우 가장 빠른 솔루션입니다. : 그것의 실행 목록에서 고유 한 값의 양에 따라 달라집니다 때문에 그 unhashable 값으로 해당 기능의 바로 최상의 경우 (그래서 대표하지 않음)의 O(n*m)
이 경우에는 단지 1 따라서 그것은에서 실행입니다 O(n)
.
면책 조항 : 나는의 저자 해요 iteration_utilities
.