คุณจะลบรายการที่ซ้ำกันออกจากรายการในขณะที่รักษาลำดับได้อย่างไร


770

มี built-in ที่ลบรายการที่ซ้ำกันออกจาก Python ในขณะที่รักษาลำดับหรือไม่ ฉันรู้ว่าฉันสามารถใช้ชุดเพื่อลบรายการที่ซ้ำกัน แต่นั่นเป็นการทำลายคำสั่งเดิม ฉันก็รู้ว่าฉันสามารถม้วนตัวเองเช่นนี้:

def uniq(input):
  output = []
  for x in input:
    if x not in output:
      output.append(x)
  return output

(ขอขอบคุณที่ผ่อนคลายสำหรับตัวอย่างโค้ดนั้น)

แต่ฉันต้องการใช้ประโยชน์จากตัวเองหรือสำนวน Pythonic เพิ่มเติมถ้าเป็นไปได้

คำถามที่เกี่ยวข้องในหลามสิ่งที่เป็นขั้นตอนวิธีที่เร็วที่สุดสำหรับการลบซ้ำกันจากรายการเพื่อให้ทุกองค์ประกอบที่ไม่ซ้ำกันในขณะที่รักษาเพื่อ ?

คำตอบ:


762

ที่นี่คุณมีทางเลือก: http://www.peterbe.com/plog/uniqifiers-benchmark

เร็วที่สุด:

def f7(seq):
    seen = set()
    seen_add = seen.add
    return [x for x in seq if not (x in seen or seen_add(x))]

ทำไมกำหนดseen.addเพื่อseen_addแทนเพียงโทรseen.add? Python เป็นภาษาแบบไดนามิกและการแก้ปัญหาการseen.addวนซ้ำแต่ละครั้งนั้นมีค่าใช้จ่ายสูงกว่าการแก้ไขตัวแปรท้องถิ่น seen.addอาจมีการเปลี่ยนแปลงระหว่างการทำซ้ำและรันไทม์ไม่ฉลาดพอที่จะออกกฎ ในการเล่นที่ปลอดภัยจะต้องตรวจสอบวัตถุทุกครั้ง

หากคุณวางแผนที่จะใช้ฟังก์ชั่นนี้จำนวนมากบนชุดข้อมูลเดียวกันบางทีคุณอาจจะดีกว่าด้วยชุดที่สั่ง: http://code.activestate.com/recipes/528878/

O (1) การแทรกการลบและการตรวจสอบสมาชิกต่อการดำเนินการ

(หมายเหตุเพิ่มเติมเล็กน้อย: seen.add()ส่งคืนเสมอNoneดังนั้นorข้างต้นมีเพียงวิธีที่จะพยายามอัปเดตชุดและไม่ได้เป็นส่วนสำคัญของการทดสอบเชิงตรรกะ)


20
@JesseDhillon seen.addอาจมีการเปลี่ยนแปลงระหว่างการทำซ้ำและรันไทม์ไม่ฉลาดพอที่จะออกกฎ ในการเล่นที่ปลอดภัยจะต้องตรวจสอบวัตถุทุกครั้ง - หากคุณดูรหัส bytecode ด้วยdis.dis(f)คุณจะเห็นว่ามันประมวลผลLOAD_ATTRสำหรับaddสมาชิกในการวนซ้ำแต่ละครั้ง ideone.com/tz1Tll
Markus Jarderot

5
เมื่อฉันลองสิ่งนี้ในรายการที่ฉันได้รับ: TypeError: unhashable ประเภท: 'list'
Jens Timmerman

7
โซลูชันของคุณไม่ใช่วิธีที่เร็วที่สุด ใน Python 3 (ไม่ได้ทดสอบ 2) นี่เร็วกว่า (รายการรายการ 300k - 0.045s (ของคุณ) เทียบกับ 0.035s (อันนี้): seen = set (); ส่งกลับ [x สำหรับ x ในบรรทัดถ้า x ไม่เห็นและไม่ใช่ see.add (x)]. ฉันไม่พบเอฟเฟกต์ความเร็วใด ๆ ของบรรทัด seen_add ที่คุณทำ
user136036

3
@ user136036 โปรดลิงค์ไปยังการทดสอบของคุณ คุณวิ่งไปกี่ครั้งแล้ว seen_addเป็นการปรับปรุง แต่การกำหนดเวลาอาจได้รับผลกระทบจากทรัพยากรระบบในเวลานั้น จะสนใจที่จะดูการกำหนดเวลาเต็มรูปแบบ
jamylak

2
สำหรับใครก็ตามที่กำลังเขียนรหัส Python คุณควรคิดสองครั้งก่อนที่จะเสียสละความสามารถในการอ่านและการประชุม Python ที่ตกลงกันโดยทั่วไปเพียงเพื่อบีบนาโนวินาทีต่อวงเพิ่มเติม การทดสอบด้วยและไม่มีseen_add = seen.addผลตอบแทนเพิ่มขึ้นเพียง 1% ในความเร็ว มันสำคัญมาก
sleblanc

343

แก้ไข 2016

ดังที่เรย์มอนด์ชี้ให้เห็นใน python 3.5+ ซึ่งOrderedDictมีการใช้งานใน C แนวทางการเข้าใจรายการจะช้ากว่าOrderedDict(เว้นแต่ว่าคุณต้องการรายการจริง ๆ ในตอนท้าย - และถึงตอนนั้นถ้าอินพุตนั้นสั้นมาก) ดังนั้นทางออกที่ดีที่สุดสำหรับ 3.5+ OrderedDictคือ

การแก้ไขที่สำคัญปี 2558

ในฐานะที่เป็น@abarnert notes more_itertoolsไลบรารี ( pip install more_itertools) จะมีunique_everseenฟังก์ชันที่สร้างขึ้นเพื่อแก้ไขปัญหานี้โดยไม่มีการกลายพันธุ์ที่ไม่สามารถอ่านได้ ( not seen.add) ในรายการความเข้าใจ นี่เป็นวิธีที่เร็วที่สุดเช่นกัน:

>>> from  more_itertools import unique_everseen
>>> items = [1, 2, 0, 1, 3, 2]
>>> list(unique_everseen(items))
[1, 2, 0, 3]

การนำเข้าไลบรารีอย่างง่ายเพียงครั้งเดียวและไม่มีแฮ็ก นี้มาจากการใช้สูตร itertools unique_everseenซึ่งมีลักษณะ:

def unique_everseen(iterable, key=None):
    "List unique elements, preserving order. Remember all elements ever seen."
    # unique_everseen('AAAABBBCCDAABBB') --> A B C D
    # unique_everseen('ABBCcAD', str.lower) --> A B C D
    seen = set()
    seen_add = seen.add
    if key is None:
        for element in filterfalse(seen.__contains__, iterable):
            seen_add(element)
            yield element
    else:
        for element in iterable:
            k = key(element)
            if k not in seen:
                seen_add(k)
                yield element

ในหลามสำนวนได้รับการยอมรับ (ซึ่งทำงานได้ แต่ไม่เหมาะสำหรับความเร็วตอนนี้ผมจะใช้) สำหรับการใช้งานนี้:2.7+unique_everseencollections.OrderedDict

ไทม์: O (N)

>>> from collections import OrderedDict
>>> items = [1, 2, 0, 1, 3, 2]
>>> list(OrderedDict.fromkeys(items))
[1, 2, 0, 3]

มันดูดีกว่า:

seen = set()
[x for x in seq if x not in seen and not seen.add(x)]

และไม่ใช้แฮ็คที่น่าเกลียด :

not seen.add(x)

ซึ่งอาศัยอยู่กับความจริงที่ว่าset.addเป็นวิธีการในสถานที่ที่มักจะส่งกลับNoneเพื่อประเมินnot NoneTrue

อย่างไรก็ตามโปรดทราบว่าโซลูชันแฮ็คจะเร็วขึ้นในความเร็วที่เป็นดิบแม้ว่ามันจะมีความซับซ้อนรันไทม์ O (N) เหมือนกัน


5
แปลงไปเป็น dict ที่กำหนดเองบางอย่างเพื่อรับกุญแจหรือไม่? แค่ไม้ยันรักแร้อีกตัว
Nakilon

3
@Nakilon ฉันไม่เห็นว่ามันเป็นไม้ยันรักแร้ มันไม่ได้เปิดเผยสถานะที่ไม่แน่นอนใด ๆ ดังนั้นมันจึงสะอาดมากในแง่นั้น ภายในชุดไพ ธ อนนั้นถูกนำไปใช้กับ dict () ( stackoverflow.com/questions/3949310/… ) ดังนั้นโดยพื้นฐานแล้วคุณแค่ทำในสิ่งที่ล่ามจะทำอยู่แล้ว
Imran

เพียงแค่ใช้ผลข้างเคียงและทำ[seen.add(x) for x in seq if x not in seen]หรือถ้าคุณไม่ชอบผลข้างเคียงที่เข้าใจเพียงแค่ใช้forลูป: for x in seq: seen.add(x) if x not in seen else None(ยังคงเป็นหนึ่งซับแม้ว่าในกรณีนี้ฉันคิดว่า one-liner-ness เป็นคุณสมบัติโง่ ๆ ที่พยายามมีใน วิธีแก้ปัญหา
ely

@EMS นั่นไม่รักษาคำสั่ง seen = set(seq)คุณอาจจะเพียงแค่เป็นอย่างดีเลย
flornquake

1
@ ComuSoft ฉันเห็นด้วยถึงแม้ว่าในทางปฏิบัติมันเกือบจะตลอดเวลา O (n) เนื่องจากเป็นกรณีที่เลวร้ายที่สุดที่ไม่น่าเป็นไปได้สูงมากอย่างยิ่ง
jamylak

110

ใน Python 2.7วิธีใหม่ในการลบรายการที่ซ้ำออกจาก iterable ในขณะที่ยังคงอยู่ในลำดับเดิมคือ:

>>> from collections import OrderedDict
>>> list(OrderedDict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']

ใน Python 3.5 OrderedDict มีการนำ C มาใช้ การกำหนดเวลาของฉันแสดงว่าตอนนี้เป็นทั้งวิธีที่เร็วและสั้นที่สุดของวิธีการต่าง ๆ สำหรับ Python 3.5

ใน Python 3.6คำสั่งปกติกลายเป็นคำสั่งและกะทัดรัด (คุณลักษณะนี้มีไว้สำหรับ CPython และ PyPy แต่อาจไม่ปรากฏในการใช้งานอื่น ๆ ) วิธีนี้ทำให้เราสามารถขจัดข้อมูลซ้ำซ้อนได้เร็วที่สุดในขณะที่ยังคงคำสั่งซื้อ:

>>> list(dict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']

ใน Python 3.7 dict ปกตินั้นรับประกันว่าจะได้รับคำสั่งทั้งการใช้งานทั้งหมด ดังนั้นทางออกที่สั้นและเร็วที่สุดคือ:

>>> list(dict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']

การตอบสนองต่อ @max: เมื่อคุณย้ายไปที่ 3.6 หรือ 3.7 และใช้ dict ปกติแทนOrderedDictคุณจะไม่สามารถเอาชนะประสิทธิภาพได้ในทางอื่น พจนานุกรมมีความหนาแน่นสูงและสามารถแปลงเป็นรายการได้โดยแทบไม่มีค่าใช้จ่าย รายการเป้าหมายมีขนาดล่วงหน้าเป็น len (d) ซึ่งจะบันทึกการปรับขนาดทั้งหมดที่เกิดขึ้นในรายการความเข้าใจ นอกจากนี้เนื่องจากรายการคีย์ภายในมีความหนาแน่นการคัดลอกพอยน์เตอร์จึงเกือบจะเร็วเหมือนการคัดลอกรายการ


มันเร็วกว่าวิธีอื่นในเครื่องของฉัน (python 3.5) ตราบใดที่ฉันไม่แปลงOrderedDictเป็นรายการในที่สุด ถ้าฉันต้องการแปลงเป็นรายการสำหรับอินพุตขนาดเล็กวิธีการเข้าใจรายการนั้นยังเร็วกว่ามากถึง 1.5 เท่า ที่กล่าวว่าวิธีนี้จะสะอาดกว่ามาก
สูงสุด

7
gotcha เพียงอย่างเดียวคือ "องค์ประกอบ" iterable จะต้อง hashable - จะดีที่ได้เทียบเท่ากับ iterables ที่มีองค์ประกอบตามอำเภอใจ (เป็นรายการของรายการ)
Mr_and_Mrs_D

การวนซ้ำของคำสั่งแทรกผ่าน dict จะให้ฟังก์ชันการทำงานที่ให้บริการกับผู้ใช้มากกว่าการลบรายการที่ซ้ำกัน ตัวอย่างเช่นการวิเคราะห์ทางวิทยาศาสตร์ขึ้นอยู่กับการคำนวณที่ทำซ้ำได้ซึ่งการทำซ้ำตามคำสั่งที่ไม่ได้กำหนดไว้ไม่สนับสนุน ความสามารถในการทำซ้ำเป็นเป้าหมายสำคัญในปัจจุบันในการสร้างแบบจำลองทางวิทยาศาสตร์การคำนวณดังนั้นเราจึงยินดีต้อนรับฟีเจอร์ใหม่นี้ แม้ว่าฉันจะรู้ว่ามันไม่สำคัญที่จะสร้างด้วย dict ที่กำหนดไว้ได้ แต่ประสิทธิภาพสูงที่กำหนดขึ้นมาset()นั้นจะช่วยให้ผู้ใช้ที่ไร้เดียงสาพัฒนาโค้ดที่ทำซ้ำได้มากขึ้น
Arthur

41
sequence = ['1', '2', '3', '3', '6', '4', '5', '6']
unique = []
[unique.append(item) for item in sequence if item not in unique]

ที่ไม่ซ้ำกัน→ ['1', '2', '3', '6', '4', '5']


28
เป็นที่น่าสังเกตว่าสิ่งนี้ทำงานในn^2
goncalopp

25
ick การโจมตี 2 ครั้ง: ใช้รายการสำหรับการทดสอบการเป็นสมาชิก (ช้า, O (N)) และใช้การทำความเข้าใจรายการสำหรับผลข้างเคียง (การสร้างรายการNoneอ้างอิงในกระบวนการอีกครั้ง!)
Martijn Pieters

1
ฉันเห็นด้วยกับ @MartijnPieters ไม่มีเหตุผลใดที่จะเข้าใจรายการด้วยผลข้างเคียง เพียงใช้การforวนซ้ำแทน
jamylak

31

ที่จะไม่เตะม้าที่ตายแล้ว (คำถามนี้เก่ามากและมีคำตอบที่ดีอยู่แล้ว) แต่นี่เป็นวิธีแก้ปัญหาโดยใช้แพนด้าที่ค่อนข้างเร็วในหลาย ๆ สถานการณ์และตายง่ายต่อการใช้งาน

import pandas as pd

my_list = [0, 1, 2, 3, 4, 1, 2, 3, 5]

>>> pd.Series(my_list).drop_duplicates().tolist()
# Output:
# [0, 1, 2, 3, 4, 5]

27
from itertools import groupby
[ key for key,_ in groupby(sortedList)]

รายการไม่จำเป็นต้องเรียงลำดับเงื่อนไขที่เพียงพอคือค่าที่เท่ากันถูกจัดกลุ่มเข้าด้วยกัน

แก้ไข: ฉันสันนิษฐานว่า "รักษาลำดับ" หมายความว่ารายการมีการสั่งซื้อจริง หากไม่เป็นเช่นนั้นโซลูชันจาก MizardX นั้นเหมาะสม

การแก้ไขโดยชุมชน: นี่เป็นวิธีที่ยอดเยี่ยมที่สุดในการ "บีบอัดองค์ประกอบที่ต่อเนื่องกันเป็นองค์ประกอบเดียว"


1
แต่นี่ไม่รักษาความสงบเรียบร้อย!

1
Hrm นี่เป็นปัญหาเนื่องจากฉันไม่สามารถรับประกันได้ว่าค่าที่เท่ากันจะถูกจัดกลุ่มเข้าด้วยกันโดยไม่วนซ้ำหนึ่งครั้งในรายการตามเวลาที่ฉันสามารถตัดส่วนที่ซ้ำกันได้
Josh Glover

ฉันสันนิษฐานว่า "รักษาลำดับ" โดยนัยว่ารายการนั้นมีการสั่งจริง
Rafał Dowgird

1
บางทีสเปคของรายการอินพุตอาจไม่ชัดเจนเล็กน้อย ค่าเหล่านั้นไม่จำเป็นต้องถูกจัดกลุ่มเข้าด้วยกัน: [2, 1, 3, 1] ดังนั้นค่าที่จะเก็บและที่จะลบ?

1
@igorkf เพิกเฉยองค์ประกอบที่สองของคู่
Rafał Dowgird

24

ฉันคิดว่าถ้าคุณต้องการรักษาระเบียบ

คุณสามารถลองสิ่งนี้:

list1 = ['b','c','d','b','c','a','a']    
list2 = list(set(list1))    
list2.sort(key=list1.index)    
print list2

หรือในทำนองเดียวกันคุณสามารถทำสิ่งนี้:

list1 = ['b','c','d','b','c','a','a']  
list2 = sorted(set(list1),key=list1.index)  
print list2 

คุณยังสามารถทำสิ่งนี้:

list1 = ['b','c','d','b','c','a','a']    
list2 = []    
for i in list1:    
    if not i in list2:  
        list2.append(i)`    
print list2

มันสามารถเขียนได้เช่นนี้

list1 = ['b','c','d','b','c','a','a']    
list2 = []    
[list2.append(i) for i in list1 if not i in list2]    
print list2 

3
คำตอบสองข้อแรกของคุณสมมติว่าลำดับของรายการสามารถสร้างใหม่ได้โดยใช้ฟังก์ชันการเรียงลำดับ แต่อาจไม่เป็นเช่นนั้น
ริชาร์ด

5
คำตอบส่วนใหญ่มุ่งเน้นไปที่ประสิทธิภาพ สำหรับรายการที่ไม่ใหญ่พอที่จะกังวลเกี่ยวกับประสิทธิภาพการเรียง (set (list1), key = list1.index) เป็นสิ่งที่ดีที่สุดที่ฉันเคยเห็น ไม่มีการนำเข้าเพิ่มเติมไม่มีฟังก์ชั่นพิเศษไม่มีตัวแปรพิเศษและมันค่อนข้างง่ายและอ่านได้
Derek Veit

23

ในPython 3.7และสูงกว่าพจนานุกรมรับประกันว่าจะจำคำสั่งแทรกของพวกเขา คำตอบสำหรับคำถามนี้สรุปสถานการณ์ปัจจุบันของกิจการ

การOrderedDictแก้ปัญหาจึงล้าสมัยและไม่มีคำชี้แจงการนำเข้าเราสามารถออก:

>>> lst = [1, 2, 1, 3, 3, 2, 4]
>>> list(dict.fromkeys(lst))
[1, 2, 3, 4]

12

สำหรับคำตอบที่ช้ามากสำหรับคำถามอื่นที่เก่ามาก:

itertoolsสูตรมีฟังก์ชั่นที่ไม่นี้โดยใช้เป็นseenเทคนิคชุด แต่:

  • จัดการkeyฟังก์ชั่นมาตรฐาน
  • ไม่ใช้แฮ็คที่ไม่สมควร
  • ปรับการวนซ้ำให้เหมาะสมโดยการผูกไว้ล่วงหน้าseen.addแทนที่จะค้นหา N ครั้ง ( f7ทำสิ่งนี้ด้วยเช่นกัน แต่บางรุ่นก็ทำไม่ได้)
  • ปรับการวนซ้ำให้เหมาะสมโดยใช้ifilterfalseดังนั้นคุณจะต้องวนซ้ำองค์ประกอบที่ไม่ซ้ำกันใน Python แทนองค์ประกอบทั้งหมด (คุณยังคงวนซ้ำพวกเขาทั้งหมดภายในifilterfalseแน่นอน แต่มันอยู่ใน C และเร็วกว่ามาก)

มันเร็วกว่าจริงf7หรือ ขึ้นอยู่กับข้อมูลของคุณดังนั้นคุณจะต้องทดสอบและดู หากคุณต้องการรายการในที่สุดf7ใช้ listcomp และไม่มีวิธีการทำที่นี่ (คุณสามารถappendแทนyieldไอเอ็นจีได้โดยตรงหรือคุณสามารถป้อนเครื่องกำเนิดไฟฟ้าเข้าไปในlistฟังก์ชั่น แต่ไม่มีใครสามารถเร็วเท่ากับ LIST_APPEND ภายใน listcomp) ในอัตราใดก็ตามโดยทั่วไปการบีบไมโครวินาทีออกไปจะไม่เท่ากับ สิ่งสำคัญคือการมีฟังก์ชั่นที่เข้าใจง่ายนำมาใช้ใหม่เขียนได้แล้วซึ่งไม่ต้องการ DSU เมื่อคุณต้องการตกแต่ง

more-iteroolsเช่นเดียวกับทุกสูตรก็ยังมีอยู่ใน

หากคุณต้องการเพียงแค่keyกรณีคุณสามารถทำให้มันง่ายขึ้นเป็น:

def unique(iterable):
    seen = set()
    seen_add = seen.add
    for element in itertools.ifilterfalse(seen.__contains__, iterable):
        seen_add(element)
        yield element

ฉันมองข้ามอย่างสิ้นเชิงmore-itertoolsนี่เป็นคำตอบที่ชัดเจนที่สุด วิธีที่ง่ายfrom more_itertools import unique_everseen list(unique_everseen(items))กว่าเร็วกว่าของฉันและดีกว่าคำตอบที่ยอมรับฉันคิดว่าการดาวน์โหลดไลบรารี่นั้นคุ้มค่า ฉันกำลังจะไปที่ชุมชนคำตอบของฉันและเพิ่มสิ่งนี้เข้ามา
jamylak

12

เพียงเพื่อเพิ่มอีก (performant มาก) การดำเนินงานของการทำงานดังกล่าวจากโมดูลภายนอก1 : iteration_utilities.unique_everseen:

>>> from iteration_utilities import unique_everseen
>>> lst = [1,1,1,2,3,2,2,2,1,3,4]

>>> list(unique_everseen(lst))
[1, 2, 3, 4]

การกำหนดเวลา

ฉันไม่ได้กำหนดเวลาที่บางคน (Python 3.6) และการแสดงเหล่านี้ว่ามันเป็นเร็วกว่าทางเลือกอื่น ๆ ทั้งหมดที่ฉันทดสอบรวมทั้งOrderedDict.fromkeys, f7และmore_itertools.unique_everseen:

%matplotlib notebook

from iteration_utilities import unique_everseen
from collections import OrderedDict
from more_itertools import unique_everseen as mi_unique_everseen

def f7(seq):
    seen = set()
    seen_add = seen.add
    return [x for x in seq if not (x in seen or seen_add(x))]

def iteration_utilities_unique_everseen(seq):
    return list(unique_everseen(seq))

def more_itertools_unique_everseen(seq):
    return list(mi_unique_everseen(seq))

def odict(seq):
    return list(OrderedDict.fromkeys(seq))

from simple_benchmark import benchmark

b = benchmark([f7, iteration_utilities_unique_everseen, more_itertools_unique_everseen, odict],
              {2**i: list(range(2**i)) for i in range(1, 20)},
              'list size (no duplicates)')
b.plot()

ป้อนคำอธิบายรูปภาพที่นี่

และเพื่อให้แน่ใจว่าฉันได้ทำการทดสอบที่ซ้ำซ้อนมากขึ้นเพื่อตรวจสอบว่ามันสร้างความแตกต่างหรือไม่:

import random

b = benchmark([f7, iteration_utilities_unique_everseen, more_itertools_unique_everseen, odict],
              {2**i: [random.randint(0, 2**(i-1)) for _ in range(2**i)] for i in range(1, 20)},
              'list size (lots of duplicates)')
b.plot()

ป้อนคำอธิบายรูปภาพที่นี่

และอีกอันมีค่าเดียวเท่านั้น:

b = benchmark([f7, iteration_utilities_unique_everseen, more_itertools_unique_everseen, odict],
              {2**i: [1]*(2**i) for i in range(1, 20)},
              'list size (only duplicates)')
b.plot()

ป้อนคำอธิบายรูปภาพที่นี่

ในทุกกรณีiteration_utilities.unique_everseenฟังก์ชันนี้เร็วที่สุด (บนคอมพิวเตอร์ของฉัน)


นี้iteration_utilities.unique_everseenฟังก์ชั่นนี้ยังสามารถจัดการกับค่า unhashable ในการป้อนข้อมูล (อย่างไรกับO(n*n)ผลการปฏิบัติงานแทนของO(n)ประสิทธิภาพการทำงานเมื่อค่าที่มี hashable)

>>> lst = [{1}, {1}, {2}, {1}, {3}]

>>> list(unique_everseen(lst))
[{1}, {2}, {3}]

1ข้อสงวนสิทธิ์: ฉันเป็นผู้เขียนแพ็คเกจนี้


ฉันไม่เข้าใจความจำเป็นสำหรับบรรทัดนี้: seen_add = seen.add- สิ่งนี้จำเป็นสำหรับการวัดประสิทธิภาพหรือไม่
Alex

@Alex นี่คือวิธีการที่กำหนดในคำตอบนี้ มันจะสมเหตุสมผลมากกว่าถ้าถามที่นั่น ฉันเพิ่งใช้วิธีจากคำตอบนั้นเพื่อเปรียบเทียบเวลา
MSeifert

คุณสามารถเพิ่มdict.fromkeys()วิธีในแผนภูมิของคุณได้ไหม?
บอริส

ฉันไม่แน่ใจจริงๆถ้าฉันมีเวลาที่จะทำในไม่ช้า คุณคิดว่ามันเร็วกว่าordereddict.fromkeysไหม?
MSeifert

"ฟังก์ชั่น iteration_utilities.unique_everseen นี้ยังสามารถจัดการค่าที่ไม่สามารถล้างได้ในอินพุต" - ใช่สิ่งนี้สำคัญมาก หากคุณมีรายการ dicts ของ dicts ของ dicts ฯลฯ นี่เป็นวิธีเดียวที่จะทำงานได้แม้ในขนาดเล็ก
Roko Mijic

6

สำหรับประเภทที่ไม่มีการแฮช (เช่นรายการของรายการ) โดยยึดตาม MizardX:

def f7_noHash(seq)
    seen = set()
    return [ x for x in seq if str( x ) not in seen and not seen.add( str( x ) )]

3

การยืมแนวคิดแบบเรียกซ้ำที่ใช้ในการกำหนดnubฟังก์ชันของ Haskell สำหรับรายการนี่จะเป็นวิธีแบบเรียกซ้ำ:

def unique(lst):
    return [] if lst==[] else [lst[0]] + unique(filter(lambda x: x!= lst[0], lst[1:]))

เช่น:

In [118]: unique([1,5,1,1,4,3,4])
Out[118]: [1, 5, 4, 3]

ฉันลองใช้เพื่อเพิ่มขนาดข้อมูลและเห็นความซับซ้อนเชิงเวลาย่อย (ไม่ชัดเจน แต่แนะนำว่าควรจะดีสำหรับข้อมูลปกติ)

In [122]: %timeit unique(np.random.randint(5, size=(1)))
10000 loops, best of 3: 25.3 us per loop

In [123]: %timeit unique(np.random.randint(5, size=(10)))
10000 loops, best of 3: 42.9 us per loop

In [124]: %timeit unique(np.random.randint(5, size=(100)))
10000 loops, best of 3: 132 us per loop

In [125]: %timeit unique(np.random.randint(5, size=(1000)))
1000 loops, best of 3: 1.05 ms per loop

In [126]: %timeit unique(np.random.randint(5, size=(10000)))
100 loops, best of 3: 11 ms per loop

ฉันยังคิดว่ามันน่าสนใจที่จะสามารถทำให้เป็นลักษณะทั่วไปได้อย่างง่ายดายโดยการดำเนินการอื่น ๆ แบบนี้:

import operator
def unique(lst, cmp_op=operator.ne):
    return [] if lst==[] else [lst[0]] + unique(filter(lambda x: cmp_op(x, lst[0]), lst[1:]), cmp_op)

ตัวอย่างเช่นคุณสามารถส่งผ่านฟังก์ชั่นที่ใช้แนวคิดของการปัดเศษเป็นจำนวนเต็มเดียวกันราวกับว่ามันเป็น "ความเท่าเทียมกัน" เพื่อจุดประสงค์ที่เป็นเอกลักษณ์เช่นนี้:

def test_round(x,y):
    return round(x) != round(y)

เฉพาะแล้ว (some_list, test_round) จะให้องค์ประกอบที่เป็นเอกลักษณ์ของรายการที่ไม่ซ้ำกันไม่ได้หมายถึงความเท่าเทียมแบบดั้งเดิมอีกต่อไป (ซึ่งมีนัยโดยการใช้วิธีการตั้งค่าใด ๆ หรือ dict-key-based) แต่แทนที่จะใช้ เฉพาะองค์ประกอบแรกที่ปัดเศษเป็น K สำหรับแต่ละเลขจำนวนเต็มที่เป็นไปได้ K ที่องค์ประกอบอาจปัดไปเช่น:

In [6]: unique([1.2, 5, 1.9, 1.1, 4.2, 3, 4.8], test_round)
Out[6]: [1.2, 5, 1.9, 4.2, 3]

1
โปรดทราบว่าประสิทธิภาพจะไม่ดีเมื่อจำนวนองค์ประกอบที่ไม่ซ้ำกันมีขนาดใหญ่มากเมื่อเทียบกับจำนวนองค์ประกอบทั้งหมดเนื่องจากการใช้การเรียกซ้ำแบบเรียกซ้ำแต่ละครั้งfilterจะไม่ได้รับประโยชน์จากการโทรก่อนหน้าเลย แต่ถ้าจำนวนขององค์ประกอบที่ไม่ซ้ำกันมีขนาดเล็กเมื่อเทียบกับขนาดอาร์เรย์นี้ควรทำงานได้ดี
ely

3

เร็วขึ้น 5 เท่าลดตัวแปร แต่ซับซ้อนยิ่งขึ้น

>>> l = [5, 6, 6, 1, 1, 2, 2, 3, 4]
>>> reduce(lambda r, v: v in r[1] and r or (r[0].append(v) or r[1].add(v)) or r, l, ([], set()))[0]
[5, 6, 1, 2, 3, 4]

คำอธิบาย:

default = (list(), set())
# use list to keep order
# use set to make lookup faster

def reducer(result, item):
    if item not in result[1]:
        result[0].append(item)
        result[1].add(item)
    return result

>>> reduce(reducer, l, default)[0]
[5, 6, 1, 2, 3, 4]

3

คุณสามารถอ้างอิงรายการความเข้าใจได้เนื่องจากมันถูกสร้างขึ้นโดยสัญลักษณ์ '_ [1]'
ตัวอย่างเช่นฟังก์ชั่นต่อไปนี้เป็นเอกลักษณ์ของรายการองค์ประกอบโดยไม่เปลี่ยนลำดับโดยอ้างอิงความเข้าใจในรายการ

def unique(my_list): 
    return [x for x in my_list if x not in locals()['_[1]']]

การสาธิต:

l1 = [1, 2, 3, 4, 1, 2, 3, 4, 5]
l2 = [x for x in l1 if x not in locals()['_[1]']]
print l2

เอาท์พุท:

[1, 2, 3, 4, 5]

2
นอกจากนี้โปรดทราบว่ามันจะทำให้เป็นการดำเนินการ O (n ^ 2) ซึ่งเป็นการสร้างชุด / dict (ซึ่งมีเวลาค้นหาคงที่) และการเพิ่มองค์ประกอบที่มองไม่เห็นก่อนหน้านี้เท่านั้นจะเป็นเส้นตรง
ely

นี่คือ Python 2.6 เท่านั้นที่ฉันเชื่อ และใช่มันคือ O (N ^ 2)
jamylak

2

คำตอบของ MizardX ให้การรวบรวมหลายวิธีที่ดี

นี่คือสิ่งที่ฉันคิดขึ้นมาในขณะที่คิดดัง ๆ :

mylist = [x for i,x in enumerate(mylist) if x not in mylist[i+1:]]

วิธีการแก้ปัญหาของคุณเป็นสิ่งที่ดี แต่มันจะปรากฏตัวครั้งสุดท้ายของแต่ละองค์ประกอบ หากต้องการใช้การปรากฏตัวครั้งแรก: [x สำหรับ i, x ในการแจกแจง (mylist) ถ้า x ไม่อยู่ในรายการของฉัน [: i]]
Rivka

7
ตั้งแต่การค้นหาในรายการเป็นการดำเนินงานและคุณดำเนินการได้ในแต่ละรายการที่ซับซ้อนที่เกิดจากการแก้ปัญหาของคุณจะO(n) O(n^2)นี่เป็นเรื่องที่ยอมรับไม่ได้สำหรับปัญหาเล็กน้อยเช่นนี้
Nikita Volkov

2

นี่เป็นวิธีง่ายๆในการทำ:

list1 = ["hello", " ", "w", "o", "r", "l", "d"]
sorted(set(list1 ), key=lambda x:list1.index(x))

ที่ให้ผลลัพธ์:

["hello", " ", "w", "o", "r", "l", "d"]

1

คุณสามารถแฮ็ครายการความเข้าใจที่น่าเกลียด

[l[i] for i in range(len(l)) if l.index(l[i]) == i]

ต้องการที่จะi,e in enumerate(l) l[i] for i in range(len(l))
Evpok

1

แนวทางที่มีประสิทธิภาพค่อนข้างมีอาร์เรย์:_sorted_numpy

b = np.array([1,3,3, 8, 12, 12,12])    
numpy.hstack([b[0], [x[0] for x in zip(b[1:], b[:-1]) if x[0]!=x[1]]])

ขาออก:

array([ 1,  3,  8, 12])

1
l = [1,2,2,3,3,...]
n = []
n.extend(ele for ele in l if ele not in set(n))

นิพจน์ตัวสร้างที่ใช้ O (1) ค้นหาชุดเพื่อพิจารณาว่าจะรวมองค์ประกอบในรายการใหม่หรือไม่


1
การใช้อย่างชาญฉลาดextendด้วยนิพจน์ตัวสร้างซึ่งขึ้นอยู่กับสิ่งที่ถูกขยาย (ดังนั้น +1) แต่set(n)จะคำนวณใหม่ในแต่ละขั้นตอน ele in nในความเป็นจริงนี้เป็นเกือบแน่นอนเลวร้ายยิ่งกว่าเพียงแค่ใช้ การสร้างชุดสำหรับการทดสอบการเป็นสมาชิกเดียวนั้นไม่คุ้มกับค่าใช้จ่ายในการสร้างชุด ยัง - มันเป็นวิธีการที่น่าสนใจ
John Coleman

1

โซลูชันแบบเรียกซ้ำง่ายๆ:

def uniquefy_list(a):
    return uniquefy_list(a[1:]) if a[0] in a[1:] else [a[0]]+uniquefy_list(a[1:]) if len(a)>1 else [a[0]]

1

กำจัดค่าที่ซ้ำกันในลำดับ แต่รักษาลำดับของรายการที่เหลือ การใช้ฟังก์ชั่นเครื่องกำเนิดไฟฟ้าเอนกประสงค์

# for hashable sequence
def remove_duplicates(items):
    seen = set()
    for item in items:
        if item not in seen:
            yield item
            seen.add(item)

a = [1, 5, 2, 1, 9, 1, 5, 10]
list(remove_duplicates(a))
# [1, 5, 2, 9, 10]



# for unhashable sequence
def remove_duplicates(items, key=None):
    seen = set()
    for item in items:
        val = item if key is None else key(item)
        if val not in seen:
            yield item
            seen.add(val)

a = [ {'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 1, 'y': 2}, {'x': 2, 'y': 4}]
list(remove_duplicates(a, key=lambda d: (d['x'],d['y'])))
# [{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 2, 'y': 4}]

1

pandas.uniqueผู้ใช้หมีแพนด้าควรตรวจสอบ

>>> import pandas as pd
>>> lst = [1, 2, 1, 3, 3, 2, 4]
>>> pd.unique(lst)
array([1, 2, 3, 4])

ฟังก์ชันส่งคืนอาร์เรย์ NumPy หากจำเป็นคุณสามารถแปลงเป็นรายการด้วยtolistวิธีการ


1
ทำได้ดีนี่. ฉันไม่เคยจินตนาการว่าจะใช้หมีแพนด้าสำหรับสิ่งนี้ แต่ใช้งานได้
seralouk

0

หากคุณต้องการหนึ่งซับแล้วอาจจะช่วย:

reduce(lambda x, y: x + y if y[0] not in x else x, map(lambda x: [x],lst))

... ควรจะทำงาน แต่แก้ไขให้ถูกต้องหากฉันทำผิด


มันเป็นการแสดงออกแบบมีเงื่อนไขดังนั้นมันดี
code22

0

หากคุณใช้เป็นประจำpandasและความสวยงามเป็นที่ต้องการมากกว่าประสิทธิภาพให้พิจารณาฟังก์ชั่นในตัวpandas.Series.drop_duplicates:

    import pandas as pd
    import numpy as np

    uniquifier = lambda alist: pd.Series(alist).drop_duplicates().tolist()

    # from the chosen answer 
    def f7(seq):
        seen = set()
        seen_add = seen.add
        return [ x for x in seq if not (x in seen or seen_add(x))]

    alist = np.random.randint(low=0, high=1000, size=10000).tolist()

    print uniquifier(alist) == f7(alist)  # True

เวลา:

    In [104]: %timeit f7(alist)
    1000 loops, best of 3: 1.3 ms per loop
    In [110]: %timeit uniquifier(alist)
    100 loops, best of 3: 4.39 ms per loop

0

สิ่งนี้จะรักษาลำดับและเรียกใช้ในเวลา O (n) โดยพื้นฐานแล้วความคิดคือการสร้างหลุมทุกที่ที่มีการค้นพบซ้ำและจมลงไปด้านล่าง ใช้ประโยชน์จากตัวชี้การอ่านและเขียน เมื่อใดก็ตามที่พบสิ่งซ้ำซ้อนตัวชี้การอ่านจะเลื่อนไปข้างหน้าและตัวชี้การเขียนจะยังคงอยู่ในรายการที่ซ้ำกันเพื่อแทนที่มัน

def deduplicate(l):
    count = {}
    (read,write) = (0,0)
    while read < len(l):
        if l[read] in count:
            read += 1
            continue
        count[l[read]] = True
        l[write] = l[read]
        read += 1
        write += 1
    return l[0:write]

0

โซลูชันโดยไม่ใช้โมดูลหรือชุดที่อิมพอร์ต:

text = "ask not what your country can do for you ask what you can do for your country"
sentence = text.split(" ")
noduplicates = [(sentence[i]) for i in range (0,len(sentence)) if sentence[i] not in sentence[:i]]
print(noduplicates)

ให้ผลลัพธ์:

['ask', 'not', 'what', 'your', 'country', 'can', 'do', 'for', 'you']

นี่คือความซับซ้อน O (N ** 2) + รายการแบ่งแต่ละครั้ง
Jean-François Fabre

0

วิธีการในสถานที่

วิธีนี้เป็นกำลังสองเนื่องจากเรามีการค้นหาเชิงเส้นในรายการสำหรับทุกองค์ประกอบของรายการ (เพื่อที่เราจะต้องเพิ่มค่าใช้จ่ายของการจัดเรียงรายการเนื่องจากdels)

ที่กล่าวว่าเป็นไปได้ที่จะทำงานในสถานที่ถ้าเราเริ่มต้นจากจุดสิ้นสุดของรายการและดำเนินการต่อไปยังจุดเริ่มต้นลบแต่ละคำที่มีอยู่ในรายการย่อยทางด้านซ้าย

ความคิดในรหัสนี้เป็นเพียงแค่

for i in range(len(l)-1,0,-1): 
    if l[i] in l[:i]: del l[i] 

การทดสอบอย่างง่ายของการนำไปใช้

In [91]: from random import randint, seed                                                                                            
In [92]: seed('20080808') ; l = [randint(1,6) for _ in range(12)] # Beijing Olympics                                                                 
In [93]: for i in range(len(l)-1,0,-1): 
    ...:     print(l) 
    ...:     print(i, l[i], l[:i], end='') 
    ...:     if l[i] in l[:i]: 
    ...:          print( ': remove', l[i]) 
    ...:          del l[i] 
    ...:     else: 
    ...:          print() 
    ...: print(l)
[6, 5, 1, 4, 6, 1, 6, 2, 2, 4, 5, 2]
11 2 [6, 5, 1, 4, 6, 1, 6, 2, 2, 4, 5]: remove 2
[6, 5, 1, 4, 6, 1, 6, 2, 2, 4, 5]
10 5 [6, 5, 1, 4, 6, 1, 6, 2, 2, 4]: remove 5
[6, 5, 1, 4, 6, 1, 6, 2, 2, 4]
9 4 [6, 5, 1, 4, 6, 1, 6, 2, 2]: remove 4
[6, 5, 1, 4, 6, 1, 6, 2, 2]
8 2 [6, 5, 1, 4, 6, 1, 6, 2]: remove 2
[6, 5, 1, 4, 6, 1, 6, 2]
7 2 [6, 5, 1, 4, 6, 1, 6]
[6, 5, 1, 4, 6, 1, 6, 2]
6 6 [6, 5, 1, 4, 6, 1]: remove 6
[6, 5, 1, 4, 6, 1, 2]
5 1 [6, 5, 1, 4, 6]: remove 1
[6, 5, 1, 4, 6, 2]
4 6 [6, 5, 1, 4]: remove 6
[6, 5, 1, 4, 2]
3 4 [6, 5, 1]
[6, 5, 1, 4, 2]
2 1 [6, 5]
[6, 5, 1, 4, 2]
1 5 [6]
[6, 5, 1, 4, 2]

In [94]:                                                                                                                             

ก่อนโพสต์ฉันได้ค้นหาเนื้อหาของคำตอบสำหรับ 'สถานที่' ที่ไม่มีประโยชน์ หากผู้อื่นแก้ไขปัญหาด้วยวิธีที่คล้ายกันโปรดแจ้งเตือนฉันและฉันจะลบคำตอบของฉันโดยเร็ว
gboffi

คุณสามารถใช้l[:] = <one of the the faster methods>ถ้าคุณต้องการการดำเนินการในสถานที่ไม่?
timgeb

@ timgeb ใช่และไม่ใช่ ... เมื่อฉันทำa=[1]; b=a; a[:]=[2]แล้วb==[2]ค่าคือTrueและเราสามารถพูดได้ว่าเรากำลังทำอยู่ในสถานที่ แต่สิ่งที่คุณเสนอคือการใช้พื้นที่ใหม่ที่จะมีรายการใหม่แทนที่ข้อมูลเก่าด้วยข้อมูลใหม่และทำเครื่องหมาย ข้อมูลเก่าสำหรับการรวบรวมขยะเพราะไม่มีการอ้างอิงอะไรอีกดังนั้นการพูดว่าการทำงานในสถานที่นั้นเป็นการยืดแนวคิดรวบยอดเล็กน้อยที่ฉันแสดงให้เห็นว่าเป็นไปได้ ... มันไม่มีประสิทธิภาพหรือไม่ ใช่ แต่ฉันบอกล่วงหน้าแล้ว
gboffi

0

วิธีการของ zmk นั้นใช้ list comprehension ซึ่งเร็วมาก แต่ยังคงความเป็นระเบียบอยู่เสมอ สำหรับการปรับใช้กับสตริงตัวพิมพ์เล็กและตัวพิมพ์ใหญ่ กรณีนี้ยังคงรักษากรณีเดิม

def DelDupes(aseq) :
    seen = set()
    return [x for x in aseq if (x.lower() not in seen) and (not seen.add(x.lower()))]

ฟังก์ชั่นที่เกี่ยวข้องอย่างใกล้ชิดคือ:

def HasDupes(aseq) :
    s = set()
    return any(((x.lower() in s) or s.add(x.lower())) for x in aseq)

def GetDupes(aseq) :
    s = set()
    return set(x for x in aseq if ((x.lower() in s) or s.add(x.lower())))

0

รายการทำความเข้าใจหนึ่งรายการ:

values_non_duplicated = [value for index, value in enumerate(values) if value not in values[ : index]]

เพียงเพิ่มเงื่อนไขเพื่อตรวจสอบว่าค่าไม่ได้อยู่ในตำแหน่งก่อนหน้า

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.