หากใช้แพ็คเกจของบุคคลที่สามจะไม่เป็นไรคุณสามารถใช้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 ยังสามารถจัดการรายการ unhashable เช่นพจนานุกรมโดยการย้อนกลับบนอัลกอริทึมช้าลง ( O(n*m)
ซึ่งn
เป็นองค์ประกอบในรายการต้นฉบับและm
องค์ประกอบที่ไม่ซ้ำกันในรายการต้นฉบับแทนO(n)
) ในกรณีที่ทั้งคีย์และค่าสามารถแฮชได้คุณสามารถใช้key
อาร์กิวเมนต์ของฟังก์ชันนั้นเพื่อสร้างรายการที่แฮชได้สำหรับ "uniqueness-test" (เพื่อให้ทำงานได้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
และแม้กระทั่งการเรียงลำดับ tuple อาจไม่ทำงานหากคีย์ไม่สามารถจัดเรียงได้:
>>> d3 = {1: 1, 'a': 'a'}
>>> tuple(sorted(d3.items()))
TypeError: '<' not supported between instances of 'str' and 'int'
เกณฑ์มาตรฐาน
ฉันคิดว่ามันอาจมีประโยชน์ที่จะเห็นว่าประสิทธิภาพของวิธีการเหล่านี้เปรียบเทียบกันอย่างไรฉันจึงทำเกณฑ์มาตรฐานเล็กน้อย กราฟเปรียบเทียบเป็นเวลาเทียบกับขนาดรายการตามรายการที่ไม่มีการทำซ้ำ (ซึ่งถูกเลือกโดยพลการรันไทม์จะไม่เปลี่ยนแปลงอย่างมีนัยสำคัญหากฉันเพิ่มบางส่วนที่ซ้ำกัน) มันเป็นพล็อตบันทึกการใช้งานเพื่อให้ครอบคลุมช่วงทั้งหมด
เวลาที่แน่นอน:
เวลาที่สัมพันธ์กับวิธีที่เร็วที่สุด:
วิธีที่สองจากthefourtheyeเร็วที่สุดที่นี่ unique_everseen
วิธีการที่มีkey
ฟังก์ชั่นที่อยู่ในสถานที่ที่สอง แต่มันเป็นวิธีที่เร็วที่สุดที่รักษาสั่งซื้อ วิธีอื่น ๆ จากjcolladoและthefourtheyeนั้นเกือบจะเร็วเหมือนกัน วิธีการที่ใช้unique_everseen
โดยไม่ต้องคีย์และการแก้ปัญหาจากเอ็มมานูและScorpilช้ามากสำหรับรายการอีกต่อไปและมีพฤติกรรมเลวร้ายมากแทนO(n*n)
stpk s approach ด้วยไม่ใช่แต่ช้ากว่าวิธีที่คล้ายกันมากO(n)
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 เพราะ runtime มันขึ้นอยู่กับปริมาณของค่าที่ไม่ซ้ำกันในรายการ: O(n*m)
ซึ่งในกรณีนี้เป็นเพียง 1 O(n)
จึงทำงานใน
Disclaimer: iteration_utilities
ผมผู้เขียน