Nếu sử dụng gói của bên thứ ba sẽ ổn thì bạn có thể sử dụng 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}]
Nó duy trì thứ tự của danh sách gốc và ut cũng có thể xử lý các mục không thể xóa được như từ điển bằng cách quay lại thuật toán chậm hơn ( O(n*m)
trong đó n
các phần tử trong danh sách gốc và m
các phần tử duy nhất trong danh sách gốc thay vì O(n)
). Trong trường hợp cả khóa và giá trị đều có thể băm, bạn có thể sử dụng key
đối số của hàm đó để tạo các mục có thể băm cho "kiểm tra tính duy nhất" (để nó hoạt động O(n)
).
Trong trường hợp từ điển (so sánh độc lập với thứ tự), bạn cần ánh xạ nó tới một cấu trúc dữ liệu khác có thể so sánh như vậy, ví dụ frozenset
:
>>> list(unique_everseen(l, key=lambda item: frozenset(item.items())))
[{'a': 123}, {'b': 123}]
Lưu ý rằng bạn không nên sử dụng một tuple
cách tiếp cận đơn giản (không sắp xếp) vì các từ điển bằng nhau không nhất thiết phải có cùng một thứ tự (ngay cả trong Python 3.7 trong đó thứ tự chèn - không phải là thứ tự tuyệt đối - được đảm bảo):
>>> d1 = {1: 1, 9: 9}
>>> d2 = {9: 9, 1: 1}
>>> d1 == d2
True
>>> tuple(d1.items()) == tuple(d2.items())
False
Và thậm chí sắp xếp bộ dữ liệu có thể không hoạt động nếu các phím không thể sắp xếp:
>>> d3 = {1: 1, 'a': 'a'}
>>> tuple(sorted(d3.items()))
TypeError: '<' not supported between instances of 'str' and 'int'
Điểm chuẩn
Tôi nghĩ rằng nó có thể hữu ích để xem hiệu suất của các phương pháp này so sánh như thế nào, vì vậy tôi đã làm một điểm chuẩn nhỏ. Các biểu đồ điểm chuẩn là thời gian so với kích thước danh sách dựa trên danh sách không chứa các mục trùng lặp (được chọn tùy ý, thời gian chạy không thay đổi đáng kể nếu tôi thêm một số hoặc nhiều bản sao). Đó là một biểu đồ log-log để phạm vi hoàn chỉnh được bao phủ.
Thời gian tuyệt đối:
Thời gian liên quan đến cách tiếp cận nhanh nhất:
Cách tiếp cận thứ hai từ thefourtheye là nhanh nhất ở đây. Cách unique_everseen
tiếp cận với key
chức năng nằm ở vị trí thứ hai, tuy nhiên đó là cách tiếp cận nhanh nhất để duy trì trật tự. Các cách tiếp cận khác từ jcollado và thefourtheye gần như nhanh chóng. Cách tiếp cận sử dụng unique_everseen
không có chìa khóa và các giải pháp từ Emmanuel và Scorpil rất chậm đối với các danh sách dài hơn và hành xử tồi tệ hơn nhiều O(n*n)
thay vì O(n)
. Cách tiếp cận của stpkjson
không phải O(n*n)
nhưng nó chậm hơn nhiều so với O(n)
cách tiếp cận tương tự .
Mã để tái tạo điểm chuẩ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)
Để hoàn thiện ở đây là thời gian cho một danh sách chỉ chứa các bản sao:
# 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)}
Thời gian không thay đổi đáng kể ngoại trừ unique_everseen
không có key
chức năng, trong trường hợp này là giải pháp nhanh nhất. Tuy nhiên, đó chỉ là trường hợp tốt nhất (không đại diện) cho hàm đó với các giá trị không thể thực hiện được vì thời gian chạy của nó phụ thuộc vào số lượng giá trị duy nhất trong danh sách: O(n*m)
trong trường hợp này chỉ là 1 và do đó, nó chạy trong O(n)
.
Tuyên bố miễn trừ trách nhiệm: Tôi là tác giả của iteration_utilities
.