Jika menggunakan paket pihak ketiga tidak apa-apa maka Anda bisa menggunakan 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}]
Ini menjaga urutan daftar asli dan ut juga dapat menangani barang-barang yang tidak dapat diakses seperti kamus dengan mundur pada algoritma yang lebih lambat (di O(n*m)
mana n
elemen dalam daftar asli dan m
elemen unik dalam daftar asli bukan O(n)
). Jika kunci dan nilai hashable, Anda dapat menggunakan key
argumen fungsi tersebut untuk membuat item hashable untuk "uji keunikan" (sehingga berfungsi O(n)
).
Dalam kasus kamus (yang membandingkan tanpa urutan) Anda perlu memetakannya ke struktur data lain yang membandingkan seperti itu, misalnya frozenset
:
>>> list(unique_everseen(l, key=lambda item: frozenset(item.items())))
[{'a': 123}, {'b': 123}]
Perhatikan bahwa Anda tidak boleh menggunakan tuple
pendekatan sederhana (tanpa pengurutan) karena kamus yang sama tidak harus memiliki urutan yang sama (bahkan dalam Python 3.7 di mana urutan penyisipan - bukan urutan absolut - dijamin):
>>> d1 = {1: 1, 9: 9}
>>> d2 = {9: 9, 1: 1}
>>> d1 == d2
True
>>> tuple(d1.items()) == tuple(d2.items())
False
Dan bahkan mengurutkan tuple mungkin tidak berfungsi jika kunci tidak dapat diurutkan:
>>> d3 = {1: 1, 'a': 'a'}
>>> tuple(sorted(d3.items()))
TypeError: '<' not supported between instances of 'str' and 'int'
Tolok ukur
Saya pikir mungkin berguna untuk melihat bagaimana kinerja pendekatan ini dibandingkan, jadi saya melakukan tolok ukur kecil. Grafik benchmark adalah waktu vs. ukuran daftar berdasarkan daftar yang tidak mengandung duplikat (yang dipilih secara sewenang-wenang, runtime tidak berubah secara signifikan jika saya menambahkan beberapa atau banyak duplikat). Ini adalah plot log-log sehingga jangkauan lengkapnya tercakup.
Waktu absolut:
Pengaturan waktu relatif terhadap pendekatan tercepat:
Pendekatan kedua dari mereka adalah yang tercepat di sini. The unique_everseen
pendekatan dengan key
fungsi di tempat kedua, namun itu pendekatan tercepat yang diawetkan memesan. Pendekatan lain dari jcollado dan theouroureye hampir sama cepatnya. Pendekatan menggunakan unique_everseen
tanpa kunci dan solusi dari Emmanuel dan Scorpil sangat lambat untuk daftar lagi dan berperilaku jauh lebih buruk O(n*n)
daripada O(n)
. Pendekatan stpk dengan json
tidak O(n*n)
tetapi itu jauh lebih lambat daripada O(n)
pendekatan serupa .
Kode untuk mereproduksi tolok ukur:
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)
Untuk kelengkapan di sini adalah waktu untuk daftar yang hanya berisi duplikat:
# 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)}
Pengaturan waktu tidak berubah secara signifikan kecuali unique_everseen
tanpa key
fungsi, yang dalam hal ini adalah solusi tercepat. Namun itu hanya kasus terbaik (jadi tidak representatif) untuk fungsi itu dengan nilai-nilai yang tidak dapat dicapai karena runtime tergantung pada jumlah nilai unik dalam daftar: O(n*m)
yang dalam hal ini hanya 1 dan karenanya berjalan dalam O(n)
.
Penafian: Saya penulis iteration_utilities
.