การลบรายการที่ซ้ำในรายการ


995

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

def remove_duplicates():
    t = ['a', 'b', 'c', 'd']
    t2 = ['a', 'c', 'd']
    for t in t2:
        t.append(t.remove())
    return t

22
คำอธิบายของคุณบอกว่าคุณตรวจสอบ "รายการ" เพื่อทำซ้ำ แต่รหัสของคุณตรวจสอบสองรายการ
Brendan Long


* using set: list (set (ELEMENTS_LIST)) * using dictionary: list (dict.fromkeys (ELEMENTS_LIST))
Shayan Amani

คำตอบ:


1641

setวิธีที่พบจะได้รับคอลเลกชันที่ไม่ซ้ำกันของรายการคือการใช้ ชุดมีเรียงลำดับคอลเลกชันที่แตกต่างกันวัตถุ ในการสร้างชุดจาก iterable ใด ๆ คุณสามารถส่งไปยังset()ฟังก์ชั่นในตัว หากคุณต้องการรายการจริงอีกครั้งในภายหลังคุณสามารถส่งชุดไปยังlist()ฟังก์ชันในทำนองเดียวกัน

ตัวอย่างต่อไปนี้ควรครอบคลุมสิ่งที่คุณพยายามทำ:

>>> t = [1, 2, 3, 1, 2, 5, 6, 7, 8]
>>> t
[1, 2, 3, 1, 2, 5, 6, 7, 8]
>>> list(set(t))
[1, 2, 3, 5, 6, 7, 8]
>>> s = [1, 2, 3]
>>> list(set(t) - set(s))
[8, 5, 6, 7]

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

การรักษาความสงบเรียบร้อย

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

>>> from collections import OrderedDict
>>> list(OrderedDict.fromkeys(t))
[1, 2, 3, 5, 6, 7, 8]

เริ่มต้นด้วย Python 3.7พจนานุกรมในตัวรับประกันการรักษาลำดับการแทรกเช่นกันดังนั้นคุณสามารถใช้โดยตรงถ้าคุณใช้ Python 3.7 หรือใหม่กว่า (หรือ CPython 3.6):

>>> list(dict.fromkeys(t))
[1, 2, 3, 5, 6, 7, 8]

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


ในที่สุดโปรดทราบว่าทั้ง / setเช่นเดียวกับOrderedDict/ dictโซลูชั่นต้องการให้รายการของคุณจะแฮชช ซึ่งมักจะหมายความว่าพวกเขาจะต้องไม่เปลี่ยนรูป หากคุณต้องจัดการกับรายการที่ไม่แฮช (เช่นรายการวัตถุ) คุณจะต้องใช้วิธีการที่ช้าซึ่งโดยทั่วไปคุณจะต้องเปรียบเทียบทุกรายการกับรายการอื่น ๆ ในวงซ้อนกัน


4
สิ่งนี้ใช้ไม่ได้กับองค์ประกอบรายการที่ล้างไม่ได้ (เช่นรายการ)
KNejad

3
@KNejad นั่นคือสิ่งที่ย่อหน้าสุดท้ายระบุ
โผล่

โอ้โห ควรจะอ่านทุกสิ่ง สิ่งที่ฉันทำลงเอยด้วยการใช้สิ่งอันดับแทนที่จะเป็นรายการเพื่อให้วิธีการนี้ยังคงใช้ได้
KNejad

เพิ่มในตัวอย่าง t = [3, 2, 1, 1, 2, 5, 6, 7, 8] แสดงความแตกต่างอย่างชัดเจน!
sailfish009

"... ค่าโสหุ้ยในการสร้างพจนานุกรมก่อน ... ถ้าคุณไม่จำเป็นต้องรักษาลำดับไว้คุณควรใช้ชุด" - ฉันทำโปรไฟล์นี้เพราะฉันสงสัยว่าจริงหรือไม่ การตั้งเวลาของฉันแสดงให้เห็นว่าแน่นอนชุดเร็วขึ้นเล็กน้อย: 1.12 µs ต่อวง (ชุด) เทียบกับ 1.53 pers ต่อวง (dict) มากกว่า 1M ลูปที่มีความแตกต่างของเวลาประมาณ 4 วินาทีในการทำซ้ำ 1M ดังนั้นหากคุณทำสิ่งนี้ในวงในที่แน่นหนาคุณอาจสนใจเป็นอย่างอื่น
millerdev

414

ใน 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']

10
ฉันคิดว่านี่เป็นวิธีเดียวที่จะเก็บของตามลำดับ
Herberth Amaral


5
@MartijnPieters การแก้ไข: ฉันคิดว่านี่เป็นวิธีเดียวที่ง่าย ๆในการเก็บรายการตามลำดับ
Herberth Amaral

11
สำหรับสิ่งนี้เช่นกันเนื้อหาของรายการดั้งเดิมจะต้องแฮช
ดาวิดี

ดังที่ @Davide กล่าวถึงรายการเดิมจะต้องแฮช ซึ่งหมายความว่าสิ่งนี้ไม่ได้ผลสำหรับรายการพจนานุกรม TypeError: unhashable type: 'dictlist'
CraZ

187

มันเป็นหนึ่งซับ: list(set(source_list))จะทำเคล็ดลับ

A setคือสิ่งที่ไม่สามารถมีซ้ำได้

อัปเดต: วิธีการเก็บรักษาใบสั่งเป็นสองบรรทัด:

from collections import OrderedDict
OrderedDict((x, True) for x in source_list).keys()

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


4
ใช้งานได้เฉพาะในกรณีที่source_listhashable
Adrian Keister

@AdrianKeister: นี่เป็นเรื่องจริง มีวัตถุที่มีความหมายเท่าเทียมกันที่สมเหตุสมผล แต่ไม่สามารถแฮชได้เช่นรายการ OTOH ถ้าเราไม่สามารถลัดอย่างเร็วได้เราจะจบด้วยอัลกอริธึมกำลังสองของการเปรียบเทียบทุกองค์ประกอบกับองค์ประกอบที่เป็นเอกลักษณ์ที่รู้จักทั้งหมดในปัจจุบัน สิ่งนี้สามารถตกลงกันได้อย่างสมบูรณ์สำหรับอินพุตสั้น ๆ โดยเฉพาะอย่างยิ่งกับการซ้ำซ้อนจำนวนมาก
9000

ถูกต้องแน่นอน ฉันคิดว่าคำตอบของคุณจะมีคุณภาพสูงขึ้นหากคุณนำกรณีการใช้งานนี้มาพิจารณาร่วมกัน
Adrian Keister

94
>>> t = [1, 2, 3, 1, 2, 5, 6, 7, 8]
>>> t
[1, 2, 3, 1, 2, 5, 6, 7, 8]
>>> s = []
>>> for i in t:
       if i not in s:
          s.append(i)
>>> s
[1, 2, 3, 5, 6, 7, 8]

33
โปรดทราบว่าวิธีนี้ใช้งานได้ในเวลา O (n ^ 2) และช้ามากในรายการขนาดใหญ่
dotancohen

@Chris_Rands: ไม่แน่ใจว่าfrozensetทำงานได้กับเนื้อหาที่ไม่สามารถแฮช ฉันยังคงได้รับข้อผิดพลาดที่ไม่ hashable frozensetเมื่อใช้
Adrian Keister

85

หากคุณไม่สนใจคำสั่งซื้อให้ทำสิ่งนี้:

def remove_duplicates(l):
    return list(set(l))

A setรับประกันว่าจะไม่ซ้ำซ้อน


3
ใช้งานไม่ได้เว้นแต่lจะแฮช
Adrian Keister

41

เพื่อสร้างรายการใหม่โดยคงลำดับขององค์ประกอบแรกของรายการที่ซ้ำกัน L

newlist=[ii for n,ii in enumerate(L) if ii not in L[:n]]

เช่นif L=[1, 2, 2, 3, 4, 2, 4, 3, 5]นั้นnewlistจะเป็น[1,2,3,4,5]

สิ่งนี้จะตรวจสอบองค์ประกอบใหม่แต่ละรายการที่ไม่ปรากฏในรายการก่อนหน้านี้ก่อนที่จะเพิ่ม นอกจากนี้มันไม่จำเป็นต้องนำเข้า


3
นี้มีความซับซ้อนของเวลาO (n ^ 2) คำตอบด้วยsetและOrderedDictอาจมีความซับซ้อนเวลาตัดจำหน่ายต่ำกว่า
blubberdiblub

ฉันใช้รหัสของฉันในการแก้ปัญหานี้และทำงานได้ดี แต่ฉันคิดว่ามันใช้เวลานาน
Gerasimos Ragavanis

@blubberdiblub คุณสามารถอธิบายได้ว่ากลไกที่มีประสิทธิภาพของรหัสเพิ่มเติมอยู่ในชุดและ OrderedDict ที่สามารถทำให้พวกเขาใช้เวลาน้อยลงหรือไม่ (ไม่รวมค่าใช้จ่ายในการโหลด)
ilias iliadis

@iliasiliadis การใช้งานปกติของชุดและdictใช้แฮชหรือต้นไม้ (บางรูปแบบของความสมดุล) คุณต้องพิจารณาการสร้างชุดหรือDictและการค้นหาในนั้น (หลายครั้ง) แต่ความซับซ้อนตัดจำหน่ายของพวกเขามักจะยังคงต่ำกว่าO (n ^ 2) "ค่าตัดจำหน่าย" ในเงื่อนไขง่ายๆหมายถึงค่าเฉลี่ย (อาจมีกรณีที่เลวร้ายที่สุดที่มีความซับซ้อนสูงกว่ากรณีทั่วไป) สิ่งนี้เกี่ยวข้องเฉพาะเมื่อคุณมีรายการจำนวนมาก
blubberdiblub

25

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

def ordered_set(in_list):
    out_list = []
    added = set()
    for val in in_list:
        if not val in added:
            out_list.append(val)
            added.add(val)
    return out_list

เพื่อเปรียบเทียบประสิทธิภาพฉันใช้ตัวอย่างสุ่มจำนวนเต็ม 100 - 62 มีเอกลักษณ์

from random import randint
x = [randint(0,100) for _ in xrange(100)]

In [131]: len(set(x))
Out[131]: 62

นี่คือผลลัพธ์ของการวัด

In [129]: %timeit list(OrderedDict.fromkeys(x))
10000 loops, best of 3: 86.4 us per loop

In [130]: %timeit ordered_set(x)
100000 loops, best of 3: 15.1 us per loop

จะเกิดอะไรขึ้นถ้าตั้งค่าถูกลบออกจากโซลูชัน

def ordered_set(inlist):
    out_list = []
    for val in inlist:
        if not val in out_list:
            out_list.append(val)
    return out_list

ผลลัพธ์ไม่เลวเท่ากับOrderedDictแต่ยังคงมีมากกว่า 3 เท่าของโซลูชันดั้งเดิม

In [136]: %timeit ordered_set(x)
10000 loops, best of 3: 52.6 us per loop

ใช้การค้นหาด่วนตั้งค่าเพื่อเพิ่มความเร็วในการเปรียบเทียบแบบวนซ้ำ หากคำสั่งซื้อไม่เกี่ยวข้องกับรายการ (ชุด (x)) จะยังคงเร็วกว่านี้ถึง 6 เท่า
Joop

@Joop นั่นเป็นคำถามแรกของฉันสำหรับเพื่อนร่วมงานของฉัน - คำสั่งไม่สำคัญ; มิฉะนั้นมันจะเป็นปัญหาเล็กน้อย
ภูเขาไฟ

รุ่นที่ปรับให้เหมาะสมของชุดคำสั่งสำหรับทุกคนที่สนใจ: def unique(iterable):; seen = set(); seen_add = seen.add; return [item for item in iterable if not item in seen and not seen_add(item)]
DrD

25

นอกจากนี้ยังมีวิธีแก้ปัญหาโดยใช้ Pandas และ Numpy พวกเขาทั้งสองกลับอาร์เรย์ numpy ดังนั้นคุณต้องใช้ฟังก์ชั่น.tolist()ถ้าคุณต้องการรายการ

t=['a','a','b','b','b','c','c','c']
t2= ['c','c','b','b','b','a','a','a']

น้ำยาแพนด้า

ใช้ฟังก์ชั่น Pandas unique():

import pandas as pd
pd.unique(t).tolist()
>>>['a','b','c']
pd.unique(t2).tolist()
>>>['c','b','a']

วิธีการแก้ปัญหาเป็นก้อน

unique()ฟังก์ชั่นการใช้ numpy

import numpy as np
np.unique(t).tolist()
>>>['a','b','c']
np.unique(t2).tolist()
>>>['a','b','c']

โปรดทราบว่า numpy.unique () จะเรียงลำดับค่าด้วย ดังนั้นรายการt2จะถูกส่งกลับเรียง หากคุณต้องการให้คำสั่งซื้อถูกเก็บไว้ใช้ในคำตอบนี้ :

_, idx = np.unique(t2, return_index=True)
t2[np.sort(idx)].tolist()
>>>['c','b','a']

วิธีแก้ปัญหานั้นไม่ได้สวยงามมากเมื่อเทียบกับรุ่นอื่น ๆ อย่างไรก็ตามเมื่อเปรียบเทียบกับ pandas.unique () numpy.unique () ช่วยให้คุณตรวจสอบว่าอาร์เรย์ที่ซ้อนกันนั้นมีลักษณะเฉพาะตามแกนที่เลือกหรือไม่


นี้จะแปลงรายการเป็นอาร์เรย์ numpy ซึ่งเป็นระเบียบและจะไม่ทำงานสำหรับสตริง
user227666

1
@ user227666 ขอบคุณสำหรับความคิดเห็นของคุณ แต่มันไม่เป็นความจริงมันทำงานได้แม้จะมีสตริงและคุณสามารถเพิ่ม. cololist ถ้าคุณต้องการที่จะได้รับรายการ ...
จีเอ็ม

1
ฉันคิดว่านี่เป็นสิ่งที่ชอบพยายามฆ่าผึ้งด้วยค้อนขนาดใหญ่ ได้ผลแน่นอน! แต่การนำเข้าไลบรารี่เพื่อจุดประสงค์นี้อาจจะเกินเลยไปหน่อยใช่ไหม?
Debosmit Ray

@DebosmitRay มันจะมีประโยชน์ถ้าคุณทำงานใน Data Science โดยปกติแล้วคุณจะทำงานกับ numpy และหลายครั้งที่คุณต้องทำงานกับ numpy array
GM

คำตอบที่ดีที่สุดในปี 2020 @DebosmitRay ฉันหวังว่าคุณจะเปลี่ยนใจและใช้หมี / แพนด้าทุกครั้งที่ทำได้
Egos

21

อีกวิธีในการทำ:

>>> seq = [1,2,3,'a', 'a', 1,2]
>> dict.fromkeys(seq).keys()
['a', 1, 2, 3]

1
โปรดทราบว่าในเวอร์ชัน Python สมัยใหม่ (2.7+ ฉันคิดว่า แต่ฉันจำไม่ได้แน่นอน) keys()ส่งคืนวัตถุมุมมองพจนานุกรมไม่ใช่รายการ
ดัสตินไวแอตต์

16

ง่ายและสะดวก:

myList = [1, 2, 3, 1, 2, 5, 6, 7, 8]
cleanlist = []
[cleanlist.append(x) for x in myList if x not in cleanlist]

เอาท์พุท:

>>> cleanlist 
[1, 2, 3, 5, 6, 7, 8]

5
อย่างไรก็ตามความซับซ้อนกำลังสอง - inคือการดำเนินการ O (n) และคุณcleanlistจะมีnตัวเลขมากที่สุด=> กรณีที่เลวร้ายที่สุด ~ O (n ^ 2)
jermenkoo

6
ไม่ควรใช้ความเข้าใจในรายการสำหรับผลข้างเคียง
Jean-François Fabre

13

ในคำตอบนี้จะเป็นสองส่วน: สองโซลูชันที่ไม่ซ้ำกันและกราฟความเร็วสำหรับโซลูชันเฉพาะ

การลบรายการซ้ำ

คำตอบส่วนใหญ่เหล่านี้เพียงลบรายการที่ซ้ำกันซึ่งแฮชได้ แต่คำถามนี้ไม่ได้หมายความว่ามันไม่เพียงต้องการไอเท็มที่สับได้ซึ่งหมายความว่าฉันจะเสนอโซลูชั่นที่ไม่ต้องใช้รายการที่แฮ

collection.Counterเป็นเครื่องมือที่มีประสิทธิภาพในไลบรารีมาตรฐานซึ่งอาจเหมาะสำหรับสิ่งนี้ มีอีกวิธีแก้ปัญหาเดียวที่มี Counter อยู่ด้วย อย่างไรก็ตามวิธีการแก้ปัญหานั้นยัง จำกัด อยู่ที่ปุ่มhashable

ในการอนุญาตให้ใช้คีย์ที่ไม่สามารถล้างได้ในตัวนับฉันได้สร้างคลาสคอนเทนเนอร์ซึ่งจะพยายามรับฟังก์ชั่นแฮชเริ่มต้นของวัตถุ แต่ถ้ามันล้มเหลวมันจะลองใช้ฟังก์ชันตัวตน นอกจากนี้ยังกำหนดEQและกัญชาวิธี นี่ควรจะเพียงพอที่จะอนุญาตรายการที่ล้างไม่ได้ในโซลูชันของเรา วัตถุที่ไม่สามารถล้างได้จะได้รับการปฏิบัติเสมือนเป็นวัตถุที่ถูกแฮช อย่างไรก็ตามฟังก์ชันแฮชนี้ใช้รหัสประจำตัวสำหรับวัตถุที่ unhashable ซึ่งหมายความว่าวัตถุสองเท่าที่ทั้งสองไม่สามารถล้างได้จะไม่ทำงาน ฉันขอแนะนำให้คุณเอาชนะสิ่งนี้และเปลี่ยนให้ใช้แฮชของประเภทที่ไม่แน่นอนที่เทียบเท่าได้ (เช่นใช้hash(tuple(my_list))ถ้าmy_listเป็นรายการ)

ฉันทำสองวิธีด้วยกัน โซลูชันอื่นที่เก็บลำดับของรายการโดยใช้คลาสย่อยของทั้ง OrderedDict และ Counter ซึ่งมีชื่อว่า 'OrderedCounter' ตอนนี้นี่คือฟังก์ชั่น:

from collections import OrderedDict, Counter

class Container:
    def __init__(self, obj):
        self.obj = obj
    def __eq__(self, obj):
        return self.obj == obj
    def __hash__(self):
        try:
            return hash(self.obj)
        except:
            return id(self.obj)

class OrderedCounter(Counter, OrderedDict):
     'Counter that remembers the order elements are first encountered'

     def __repr__(self):
         return '%s(%r)' % (self.__class__.__name__, OrderedDict(self))

     def __reduce__(self):
         return self.__class__, (OrderedDict(self),)

def remd(sequence):
    cnt = Counter()
    for x in sequence:
        cnt[Container(x)] += 1
    return [item.obj for item in cnt]

def oremd(sequence):
    cnt = OrderedCounter()
    for x in sequence:
        cnt[Container(x)] += 1
    return [item.obj for item in cnt]

remd คือการเรียงลำดับที่ไม่ได้รับคำสั่ง oremd ถูกเรียงลำดับแล้วเรียงลำดับ คุณสามารถบอกได้ชัดเจนว่าอันไหนเร็วกว่า แต่ฉันจะอธิบายต่อไป การเรียงลำดับที่ไม่ได้รับคำสั่งนั้นเร็วกว่าเล็กน้อย มันเก็บข้อมูลน้อยลงเนื่องจากไม่ต้องการสั่งซื้อ

ตอนนี้ฉันยังต้องการแสดงการเปรียบเทียบความเร็วของคำตอบแต่ละข้อด้วย ดังนั้นฉันจะทำตอนนี้

ฟังก์ชั่นใดเร็วที่สุด?

สำหรับการลบรายการที่ซ้ำกันฉันรวบรวม 10 ฟังก์ชั่นจากคำตอบเล็กน้อย ฉันคำนวณความเร็วของแต่ละฟังก์ชั่นและใส่ลงในกราฟโดยใช้matplotlib.pyplot matplotlib.pyplot

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

Unordered Hashableสำหรับวิธีการใด ๆ ที่นำรายการที่ซ้ำกันออกซึ่งไม่จำเป็นต้องเก็บคำสั่งซื้อไว้ มันไม่ต้องทำงานเพื่อ unhashables แต่ก็ทำได้

Hashable ที่สั่งไว้มีไว้สำหรับวิธีการใด ๆ ที่รักษาลำดับของรายการไว้ในรายการ แต่ไม่จำเป็นต้องใช้สำหรับ unhashables แต่สามารถทำได้

สั่ง Unhashableเป็นวิธีการใด ๆ ที่เก็บคำสั่งของรายการในรายการและทำงานเพื่อ unhashables

บนแกน y คือจำนวนวินาทีที่ใช้

บนแกน x คือหมายเลขที่ฟังก์ชันใช้งาน

เราสร้างลำดับสำหรับ hashables ที่ไม่ได้เรียงลำดับและสั่ง hashables ด้วยความเข้าใจดังต่อไปนี้: [list(range(x)) + list(range(x)) for x in range(0, 1000, 10)]

สำหรับ unhashables ที่สั่งซื้อ: [[list(range(y)) + list(range(y)) for y in range(x)] for x in range(0, 1000, 10)]

โปรดทราบว่ามี 'ขั้นตอน' อยู่ในช่วงเนื่องจากไม่มีมันจะใช้เวลานานถึง 10 เท่า เพราะในความเห็นส่วนตัวของฉันฉันคิดว่ามันอาจจะดูง่ายกว่าเล็กน้อยในการอ่าน

ยังทราบว่าปุ่มต่างๆในตำนานนั้นเป็นสิ่งที่ฉันพยายามจะเดาว่าเป็นส่วนที่สำคัญที่สุดของฟังก์ชั่น ฟังก์ชั่นใดที่แย่ที่สุดหรือดีที่สุด? กราฟพูดด้วยตนเอง

เมื่อตัดสินแล้วนี่คือกราฟ

Hashables ที่ไม่ได้สั่ง

ป้อนคำอธิบายรูปภาพที่นี่ (ซูมเข้า) ป้อนคำอธิบายรูปภาพที่นี่

Hashables สั่ง

ป้อนคำอธิบายรูปภาพที่นี่ (ซูมเข้า) ป้อนคำอธิบายรูปภาพที่นี่

สั่ง Unhashables

ป้อนคำอธิบายรูปภาพที่นี่ (ซูมเข้า) ป้อนคำอธิบายรูปภาพที่นี่


11

ฉันมี dict ในรายการของฉันดังนั้นฉันจึงไม่สามารถใช้วิธีการข้างต้นได้ ฉันได้รับข้อผิดพลาด:

TypeError: unhashable type:

ดังนั้นหากคุณสนใจสั่งซื้อและ / หรือบางรายการจะไม่สามารถซักได้ จากนั้นคุณอาจพบว่ามีประโยชน์นี้:

def make_unique(original_list):
    unique_list = []
    [unique_list.append(obj) for obj in original_list if obj not in unique_list]
    return unique_list

บางคนอาจพิจารณาความเข้าใจของรายการโดยมีผลข้างเคียงไม่ใช่วิธีแก้ปัญหาที่ดี นี่คือทางเลือก:

def make_unique(original_list):
    unique_list = []
    map(lambda x: unique_list.append(x) if (x not in unique_list) else False, original_list)
    return unique_list

6
mapกับผลข้างเคียงจะยิ่งทำให้เข้าใจผิดยิ่งกว่า listcomp กับผลข้างเคียง นอกจากนี้ยังlambda x: unique_list.append(x)เป็นเพียง clunkier unique_list.appendและวิธีการที่จะผ่านช้าลง
abarnert

วิธีที่มีประโยชน์มากในการผนวกองค์ประกอบในหนึ่งบรรทัดขอบคุณ!
ZLNK

2
@ZLNK ได้โปรดอย่าใช้มัน นอกเหนือจากการมีแนวคิดที่น่าเกลียดแล้วมันยังไม่มีประสิทธิภาพอย่างมากเพราะคุณสร้างรายการที่มีขนาดใหญ่และโยนทิ้งไปเพื่อทำซ้ำขั้นพื้นฐาน
Eli Korvigo

10

วิธีการรักษาลำดับทั้งหมดที่ฉันเคยเห็นที่นี่จนถึงใช้การเปรียบเทียบแบบไร้เดียงสา (กับ O (n ^ 2) ความซับซ้อนของเวลาที่ดีที่สุด) หรือหนักOrderedDicts/ set+listชุดค่าผสมที่ จำกัด เฉพาะอินพุตแบบแฮช นี่คือโซลูชัน O (nlogn) แบบแฮชอิสระ:

อัปเดตเพิ่มkeyอาร์กิวเมนต์เอกสารและความเข้ากันได้ของ Python 3

# from functools import reduce <-- add this import on Python 3

def uniq(iterable, key=lambda x: x):
    """
    Remove duplicates from an iterable. Preserves order. 
    :type iterable: Iterable[Ord => A]
    :param iterable: an iterable of objects of any orderable type
    :type key: Callable[A] -> (Ord => B)
    :param key: optional argument; by default an item (A) is discarded 
    if another item (B), such that A == B, has already been encountered and taken. 
    If you provide a key, this condition changes to key(A) == key(B); the callable 
    must return orderable objects.
    """
    # Enumerate the list to restore order lately; reduce the sorted list; restore order
    def append_unique(acc, item):
        return acc if key(acc[-1][1]) == key(item[1]) else acc.append(item) or acc 
    srt_enum = sorted(enumerate(iterable), key=lambda item: key(item[1]))
    return [item[1] for item in sorted(reduce(append_unique, srt_enum, [srt_enum[0]]))] 

แต่โซลูชันนี้ต้องการองค์ประกอบที่สามารถสั่งซื้อได้ ฉันจะใช้รายการของฉันเป็นรายการที่ไม่ซ้ำกัน: มันเป็นความเจ็บปวดในtuple()รายการและแฮชพวกเขา | | | | - โดยทั่วไปกระบวนการแฮชจะใช้เวลาเป็นสัดส่วนกับขนาดของข้อมูลทั้งหมดในขณะที่โซลูชันนี้ใช้เวลา O (nlog (n)) ขึ้นอยู่กับความยาวของรายการเท่านั้น
loxaxs

ฉันคิดว่าวิธีการตั้งค่ามีราคาถูกเท่ากัน (O (n log n)) หรือถูกกว่าการเรียงลำดับ + การตรวจจับวัตถุโบราณ (วิธีนี้จะทำให้ดีขึ้นเป็นคู่ขนานมากขึ้น) แต่ก็ไม่ได้รักษาลำดับเริ่มต้นอย่างแน่นอน แต่จะให้ลำดับที่คาดเดาได้
9000

@ 9000 นั่นคือความจริง ฉันไม่เคยพูดถึงความซับซ้อนของเวลาของวิธีการแฮชตารางซึ่งเป็น O (n) ที่นี่คุณจะพบคำตอบมากมายที่รวมไว้ในตารางแฮช แม้ว่ามันจะไม่เป็นสากลเพราะมันต้องการวัตถุที่แฮช ยิ่งไปกว่านั้นมันยังเพิ่มความจำอย่างมาก
Eli Korvigo

ใช้เวลาในการอ่านและทำความเข้าใจคำตอบนี้ มีจุดในการแจกแจงเมื่อคุณไม่ได้ใช้ดัชนีหรือไม่? reduce() อยู่แล้วทำงานในคอลเลกชันที่เรียงลำดับsrt_enumทำไมคุณไม่ใช้sortedอีกครั้งหรือไม่
Brayoni

@Brayoni การเรียงลำดับแรกคือการจัดกลุ่มค่าที่เท่ากันการเรียงลำดับที่สองจะมีการเรียกคืนลำดับเริ่มต้น จำเป็นต้องมีการแจงนับเพื่อติดตามลำดับญาติดั้งเดิม
Eli Korvigo

9

หากคุณต้องการรักษาลำดับและไม่ใช้โมดูลภายนอกที่นี่เป็นวิธีที่ง่ายในการทำเช่นนี้:

>>> t = [1, 9, 2, 3, 4, 5, 3, 6, 7, 5, 8, 9]
>>> list(dict.fromkeys(t))
[1, 9, 2, 3, 4, 5, 6, 7, 8]

หมายเหตุ: วิธีนี้จะรักษาลำดับของการปรากฏดังนั้นตามที่เห็นด้านบนเก้าจะมาหนึ่งเพราะมันเป็นครั้งแรกที่มันปรากฏ อย่างไรก็ตามนี่เป็นผลลัพธ์เดียวกับที่คุณทำ

from collections import OrderedDict
ulist=list(OrderedDict.fromkeys(l))

แต่มันสั้นกว่ามากและทำงานได้เร็วกว่า

สิ่งนี้ได้ผลเพราะทุกครั้งที่fromkeysฟังก์ชั่นพยายามสร้างคีย์ใหม่หากมีค่าอยู่แล้วมันก็จะเขียนทับมัน สิ่งนี้จะไม่ส่งผลกระทบต่อพจนานุกรม แต่อย่างใดเนื่องจากfromkeysจะสร้างพจนานุกรมที่คีย์ทั้งหมดมีค่าNoneดังนั้นจึงเป็นการกำจัดรายการที่ซ้ำทั้งหมดด้วยวิธีนี้



8

คุณสามารถทำได้เช่นกัน:

>>> t = [1, 2, 3, 3, 2, 4, 5, 6]
>>> s = [x for i, x in enumerate(t) if i == t.index(x)]
>>> s
[1, 2, 3, 4, 5, 6]

เหตุผลที่ทำงานข้างต้นเป็นindexวิธีการที่ส่งกลับเฉพาะดัชนีแรกขององค์ประกอบ องค์ประกอบที่ซ้ำกันมีดัชนีสูงขึ้น อ้างถึงที่นี่ :

list.index (x [, start [, end]])
กลับดัชนี zero-based ในรายการของรายการแรกที่มีค่าเป็น x เพิ่ม ValueError หากไม่มีรายการดังกล่าว


มันไม่มีประสิทธิภาพอย่างน่ากลัว list.indexเป็นการดำเนินการเชิงเส้นเวลาทำให้โซลูชันของคุณเป็นกำลังสอง
Eli Korvigo

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


7

ลดความแตกต่างด้วยการสงวนรักษาลำดับ:

สมมติว่าเรามีรายการ:

l = [5, 6, 6, 1, 1, 2, 2, 3, 4]

ลดความแตกต่าง (ไม่มีประสิทธิภาพ):

>>> reduce(lambda r, v: v in r and r or r + [v], l, [])
[5, 6, 1, 2, 3, 4]

เร็วกว่า 5 เท่า แต่ซับซ้อนกว่า

>>> 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())
# user 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]

7

วิธีที่ดีที่สุดในการลบรายการที่ซ้ำกันออกจากรายการคือการใช้ฟังก์ชั่นset () ที่มีอยู่ในไพ ธ อนการแปลงชุดนั้นเป็นรายการอีกครั้ง

In [2]: some_list = ['a','a','v','v','v','c','c','d']
In [3]: list(set(some_list))
Out[3]: ['a', 'c', 'd', 'v']

@MeetZaveri ดีใจ!
Anurag Misra

การยกตัวอย่างรายการและชุดใหม่นั้นไม่ได้ฟรี จะเกิดอะไรขึ้นถ้าเราทำเช่นนี้หลายครั้งอย่างต่อเนื่อง (เช่นในวงแคบ ๆ ) และรายการมีขนาดเล็กมาก?
Z4 เทียร์

6

คุณสามารถใช้ฟังก์ชั่นต่อไปนี้:

def rem_dupes(dup_list): 
    yooneeks = [] 
    for elem in dup_list: 
        if elem not in yooneeks: 
            yooneeks.append(elem) 
    return yooneeks

ตัวอย่าง :

my_list = ['this','is','a','list','with','dupicates','in', 'the', 'list']

การใช้งาน:

rem_dupes(my_list)

['this', 'is', 'a', 'list', 'with', 'dupicates', 'in', 'the']


5

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

def uniqify(iterable):
    seen = set()
    for item in iterable:
        if item not in seen:
            seen.add(item)
            yield item

สิ่งนี้จะคืนค่าตัวสร้าง / ตัววนซ้ำดังนั้นคุณจึงสามารถใช้งานได้ทุกที่ที่คุณสามารถใช้ตัววนซ้ำได้

for unique_item in uniqify([1, 2, 3, 4, 3, 2, 4, 5, 6, 7, 6, 8, 8]):
    print(unique_item, end=' ')

print()

เอาท์พุท:

1 2 3 4 5 6 7 8

หากคุณต้องการ a listคุณสามารถทำได้:

unique_list = list(uniqify([1, 2, 3, 4, 3, 2, 4, 5, 6, 7, 6, 8, 8]))

print(unique_list)

เอาท์พุท:

[1, 2, 3, 4, 5, 6, 7, 8]

seen = set(iterable); for item in seen: yield itemเกือบจะเร็วกว่าแน่นอน (ฉันไม่ได้ลองกรณีเฉพาะนี้ แต่นั่นอาจเป็นการคาดเดาของฉัน)
dylnmc

2
@dylnmc นั่นคือการดำเนินการแบทช์และยังสูญเสียการสั่งซื้อ คำตอบของฉันได้รับการออกแบบมาโดยเฉพาะให้เป็นไปตามลำดับและเกิดขึ้นเป็นครั้งแรก :)
Cyphase

5

โดยไม่ต้องใช้ชุด

data=[1, 2, 3, 1, 2, 5, 6, 7, 8]
uni_data=[]
for dat in data:
    if dat not in uni_data:
        uni_data.append(dat)

print(uni_data) 

5

คุณสามารถใช้setเพื่อลบรายการซ้ำ:

mylist = list(set(mylist))

แต่โปรดทราบว่าผลลัพธ์จะไม่มีการเรียงลำดับ หากเป็นปัญหา:

mylist.sort()

1
คุณสามารถทำได้: mylist = sort (รายการ (set (mylist)))
Erik Campobadal

5

อีกวิธีที่ดีกว่าก็คือ

import pandas as pd

myList = [1, 2, 3, 1, 2, 5, 6, 7, 8]
cleanList = pd.Series(myList).drop_duplicates().tolist()
print(cleanList)

#> [1, 2, 3, 5, 6, 7, 8]

และคำสั่งยังคงเก็บรักษาไว้


แม้ว่าสิ่งนี้อาจทำงานได้ดี แต่การใช้ไลบรารี่ขนาดใหญ่เช่นหมีแพนด้าเพื่อจุดประสงค์นี้ดูเหมือนว่าเกินความเป็นจริง
Glutexo

4

อันนี้ใส่ใจเกี่ยวกับการสั่งซื้อโดยไม่ยุ่งยากมากเกินไป (OrderdDict & อื่น ๆ ) อาจไม่ใช่วิธี Pythonic ที่มากที่สุดหรือวิธีที่สั้นที่สุด แต่ใช้เคล็ดลับ:

def remove_duplicates(list):
    ''' Removes duplicate items from a list '''
    singles_list = []
    for element in list:
        if element not in singles_list:
            singles_list.append(element)
    return singles_list

1. คุณไม่ควรใช้ชื่อเงาในตัว (อย่างน้อยก็สำคัญlist) 2. เครื่องชั่งน้ำหนักวิธีการของคุณไม่ดีมาก: listมันเป็นกำลังสองในจำนวนขององค์ประกอบใน
Eli Korvigo

1. ถูกต้อง แต่นี่เป็นตัวอย่าง 2. ถูกต้องและนั่นคือเหตุผลที่ฉันเสนอให้ โซลูชันทั้งหมดที่โพสต์ที่นี่มีข้อดีข้อเสีย บางคนเสียสละความเรียบง่ายหรือคำสั่งเสียสละความยืดหยุ่นของฉัน
cgf

นี่เป็นอัลกอริธึม "Shlemiel the painter" ...
Z4-tier

4

โค้ดด้านล่างนั้นง่ายสำหรับการลบรายการที่ซ้ำกัน

def remove_duplicates(x):
    a = []
    for i in x:
        if i not in a:
            a.append(i)
    return a

print remove_duplicates([1,2,2,3,3,4])

มันจะส่งกลับ [1,2,3,4]


2
หากคุณไม่สนใจคำสั่งซื้อสิ่งนี้จะใช้เวลานานกว่ามาก list(set(..))(มากกว่า 1 ล้านรอบ) จะเอาชนะโซลูชันนี้ประมาณ 10 วินาทีทั้งหมด - ในขณะที่วิธีนี้ใช้เวลาประมาณ 12 วินาทีlist(set(..))ใช้เวลาประมาณ 2 วินาทีเท่านั้น!
dylnmc

@dylnmc นี่ก็เป็นคำตอบที่
Eli Korvigo

4

นี่คือวิธีการแก้ปัญหาแบบ pythonic ที่เร็วที่สุดซึ่งเทียบได้กับรายการอื่นในการตอบกลับ

การใช้รายละเอียดการใช้งานของการประเมินการลัดวงจรช่วยให้สามารถใช้ list comprehension ซึ่งเร็วพอ visited.add(item)ส่งคืนผลลัพธ์เสมอNoneซึ่งจะถูกประเมินเป็นFalseดังนั้นด้านขวาของorจะเป็นผลลัพธ์ของนิพจน์ดังกล่าวเสมอ

เวลาตัวเอง

def deduplicate(sequence):
    visited = set()
    adder = visited.add  # get rid of qualification overhead
    out = [adder(item) or item for item in sequence if item not in visited]
    return out


4

น่าเสียดาย. คำตอบส่วนใหญ่ที่นี่ไม่รักษาคำสั่งซื้อหรือยาวเกินไป นี่คือคำตอบง่ายๆที่เก็บรักษาไว้

s = [1,2,3,4,5,2,5,6,7,1,3,9,3,5]
x=[]

[x.append(i) for i in s if i not in x]
print(x)

สิ่งนี้จะให้คุณ x ลบรายการที่ซ้ำกัน แต่รักษาลำดับ


3

วิธีที่ง่ายมากใน Python 3:

>>> n = [1, 2, 3, 4, 1, 1]
>>> n
[1, 2, 3, 4, 1, 1]
>>> m = sorted(list(set(n)))
>>> m
[1, 2, 3, 4]

2
sorted(list(...))เป็นซ้ำซ้อน ( sortedโดยปริยายแล้วแปลงอาร์กิวเมนต์เป็นใหม่listแล้วเรียงลำดับแล้วส่งกลับใหม่listดังนั้นการใช้ทั้งสองวิธีจึงเป็นการชั่วคราวที่ไม่จำเป็นlist) ใช้เฉพาะในlistกรณีที่ผลลัพธ์ไม่จำเป็นต้องมีการเรียงลำดับใช้เฉพาะในsortedกรณีที่จำเป็นต้องจัดเรียงผลลัพธ์
ShadowRanger

3

ประเภทของ Magic of Python ในตัว

ในไพ ธ อนมันเป็นเรื่องง่ายมากที่จะประมวลผลกรณีที่ซับซ้อนเช่นนี้และตามประเภทในตัวของไพ ธ อนเท่านั้น

ให้ฉันแสดงวิธีทำ!

วิธีที่ 1: กรณีทั่วไป

วิธี ( รหัส 1 บรรทัด ) เพื่อลบองค์ประกอบที่ซ้ำกันในรายการและยังคงเรียงลำดับ

line = [1, 2, 3, 1, 2, 5, 6, 7, 8]
new_line = sorted(set(line), key=line.index) # remove duplicated element
print(new_line)

คุณจะได้รับผลลัพธ์

[1, 2, 3, 5, 6, 7, 8]

วิธีที่ 2: กรณีพิเศษ

TypeError: unhashable type: 'list'

กรณีพิเศษในการดำเนินการ unhashable ( 3 บรรทัดรหัส )

line=[['16.4966155686595', '-27.59776154691', '52.3786295521147']
,['16.4966155686595', '-27.59776154691', '52.3786295521147']
,['17.6508629295574', '-27.143305738671', '47.534955022564']
,['17.6508629295574', '-27.143305738671', '47.534955022564']
,['18.8051102904552', '-26.688849930432', '42.6912804930134']
,['18.8051102904552', '-26.688849930432', '42.6912804930134']
,['19.5504702331098', '-26.205884452727', '37.7709192714727']
,['19.5504702331098', '-26.205884452727', '37.7709192714727']
,['20.2929416861422', '-25.722717575124', '32.8500163147157']
,['20.2929416861422', '-25.722717575124', '32.8500163147157']]

tuple_line = [tuple(pt) for pt in line] # convert list of list into list of tuple
tuple_new_line = sorted(set(tuple_line),key=tuple_line.index) # remove duplicated element
new_line = [list(t) for t in tuple_new_line] # convert list of tuple into list of list

print (new_line)

คุณจะได้รับผล:

[
  ['16.4966155686595', '-27.59776154691', '52.3786295521147'], 
  ['17.6508629295574', '-27.143305738671', '47.534955022564'], 
  ['18.8051102904552', '-26.688849930432', '42.6912804930134'], 
  ['19.5504702331098', '-26.205884452727', '37.7709192714727'], 
  ['20.2929416861422', '-25.722717575124', '32.8500163147157']
]

เนื่องจาก tuple สามารถแฮชและคุณสามารถแปลงข้อมูลระหว่าง list และ tuple ได้อย่างง่ายดาย

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