การลบรายการไพ ธ อน


227

ฉันต้องการทำสิ่งที่คล้ายกับสิ่งนี้:

>>> x = [1,2,3,4,5,6,7,8,9,0]  
>>> x  
[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]  
>>> y = [1,3,5,7,9]  
>>> y  
[1, 3, 5, 7, 9]  
>>> y - x   # (should return [2,4,6,8,0])

แต่นี่ไม่ได้รับการสนับสนุนโดยรายการหลามวิธีที่ดีที่สุดในการทำคืออะไร?


@ezdazuzena สิ่งนี้ไม่ใช่การลบ นี่คือความแตกต่างระหว่างสองรายการ การแบ่งปันของคุณไม่ใช่การเผยแพร่ต่อของคำถามนี้
Celik

1
[2, 2] - [2] ควรส่งคืนอย่างไร []? [2]?
แมคเคย์

@McKay [2,2] - [2] ควรกลับ [2] [2,2] - [1,2,2,3] ควรกลับ []
Robino

คำถามนี้เกี่ยวกับการลบรายการ แต่คำตอบที่ยอมรับนั้นอยู่ใกล้กับการลบการตั้งค่า
Robino

2
[2, 1, 2, 3, 2, 4, 2] - [2, 3, 2] ผลตอบแทนคืออะไรและทำไม? มันควรจะหา 232 ตรงกลางแล้วคืน 2142 หรือไม่ หรือควรหาครั้งแรกในแต่ละครั้งและกลับ 1242? หรืออย่างอื่น? สิ่งที่ฉันพูดคือสิ่งเหล่านี้ไม่ใช่คำตอบที่ชัดเจนและขึ้นอยู่กับความต้องการ
แมคเคย์

คำตอบ:


330

ใช้รายการความเข้าใจ:

[item for item in x if item not in y]

หากคุณต้องการใช้-ไวยากรณ์ infix คุณสามารถทำได้:

class MyList(list):
    def __init__(self, *args):
        super(MyList, self).__init__(args)

    def __sub__(self, other):
        return self.__class__(*[item for item in self if item not in other])

จากนั้นคุณสามารถใช้งานได้เช่น:

x = MyList(1, 2, 3, 4)
y = MyList(2, 5, 2)
z = x - y   

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


10
@admica ไม่ใช้listสำหรับชื่อตัวแปรเนื่องจากเป็นตัวlistสร้างเงา หากคุณใช้ 'รายการ' โปรดนำหน้าด้วยเครื่องหมายขีดล่าง นอกจากนี้โดยทิ้ง*คุณยากจนรหัสของฉัน ...
aaronasterling

19
ถ้าคุณทำ[1,1,2,2] - [1,2]คุณจะได้รับรายการที่ว่างเปล่า [1,1,2,2] - [2]ให้[1,1]ดังนั้นจึงไม่ได้จริงๆรายการ Substraction ก็จะมากขึ้นเช่นรายการ "จากรายการXโดยไม่ต้ององค์ประกอบจากชุดY "
Alfred Zien

@AlfredZien สิ่งที่เขาพูด
RetroCode

รายการความเข้าใจวิธีการเป็นวิธีที่ช้าลง (ในตัวอย่างของฉัน) กว่าวิธีการตั้งค่าความแตกต่าง
redfiloux

1
@BarnabasSzabolcs: นั่นจะไม่บันทึกสิ่งใดเพราะมันจะแปลงyเป็นsetก่อนทุกการตรวจสอบ (ซึ่งเป็นค่าใช้จ่ายคล้ายกับงานต้นฉบับ) คุณจะต้องทำyset = set(y)นอก listcomp จากนั้นทดสอบif item not in ysetหรือเป็นแฮ็คที่ร้ายแรง[item for yset in [set(y)] for item in x if item not in yset]ซึ่งทำสิ่งที่ไม่เหมาะสมที่ซ้อน listcomps เพื่อแคชในysetฐานะหนึ่งซับ วิธีแก้ปัญหาหนึ่งซับน้อยที่น่าเกลียดน้อยที่มีประสิทธิภาพเพียงพอที่จะใช้list(itertools.filterfalse(set(y).__contains__, x))เพราะอาร์กิวเมนต์ที่filterfalseจะสร้างเพียงครั้งเดียว
ShadowRanger

259

ใช้ความแตกต่างของชุด

>>> z = list(set(x) - set(y))
>>> z
[0, 8, 2, 4, 6]

หรือคุณอาจตั้งค่า x และ y เพื่อให้คุณไม่ต้องทำการแปลงใด ๆ


50
สิ่งนี้จะสูญเสียการสั่งซื้อใด ๆ ที่อาจหรืออาจไม่สำคัญขึ้นอยู่กับบริบท
aaronasterling

63
สิ่งนี้จะทำให้ข้อมูลที่ซ้ำกันที่อาจเป็นไปได้สูญเสียซึ่งอาจต้องการ / ต้องการการบำรุงรักษา
โอปอล

ฉันได้TypeError: unhashable type: 'dict'
Havnar

นี่เป็นวิธีที่เร็วกว่าในกรณีที่การเปรียบเทียบรายการมีขนาดใหญ่
JqueryToAddNumbers

2
หากการสั่งซื้อและการทำซ้ำของรายการในรายการไม่สำคัญกับบริบทนี่เป็นคำตอบที่ดีและมันอ่านได้ง่ายมาก
Watt

37

นั่นคือการดำเนินการ "การลบการตั้งค่า" ใช้โครงสร้างข้อมูลที่กำหนดไว้สำหรับสิ่งนั้น

ใน Python 2.7:

x = {1,2,3,4,5,6,7,8,9,0}
y = {1,3,5,7,9}
print x - y

เอาท์พุท:

>>> print x - y
set([0, 8, 2, 4, 6])

1
list (set ([1,2,3,4,5]) - set ([1,2,3])) = [4, 5] เพื่อให้รายการแต่ละรายการตั้งค่าก่อนจากนั้นลบ (หรือ one-way diff) ) และกลับสู่รายการ
gseattle

2
ไม่ดีถ้าคุณต้องการรักษาลำดับรายการดั้งเดิมของชุด x
Zahran

34

หากรายการที่ซ้ำกันและการสั่งซื้อเป็นปัญหา:

[i for i in a if not i in b or b.remove(i)]

a = [1,2,3,3,3,3,4]
b = [1,3]
result: [2, 3, 3, 3, 4]

2
ใช้งานได้แม้ว่าจะเป็นO(m * n)runtime (และฉันประจบประแจงเมื่อใดก็ตามที่ listcomp มีผลข้างเคียง); คุณสามารถปรับปรุงให้ดีขึ้นโดยใช้collections.Counterเพื่อเรียกO(m + n)ใช้งานจริง
ShadowRanger

ฉันมีความเข้าใจยากในเรื่องนี้ใครสามารถอธิบายได้บ้าง
anushka

20

สำหรับหลายกรณีการใช้งานคำตอบที่คุณต้องการคือ:

ys = set(y)
[item for item in x if item not in ys]

นี่คือลูกผสมระหว่างคำตอบของ aaronasterlingและคำตอบของ quantumSoupคำตอบของ

เวอร์ชันของ aaronasterling ทำการlen(y)เปรียบเทียบรายการสำหรับแต่ละองค์ประกอบในxดังนั้นจึงใช้เวลากำลังสอง รุ่นของ quantumSoup ใช้ชุดดังนั้นมันจึงทำการค้นหาชุดเวลาคงที่เดียวสำหรับแต่ละองค์ประกอบใน - xแต่เพราะมันแปลงทั้งสอง xและyเป็นเซตมันเสียลำดับขององค์ประกอบของคุณ

ด้วยการแปลงyเป็นชุดเท่านั้นและวนซ้ำxตามลำดับคุณจะได้รับสิ่งที่ดีที่สุดทั้งสองโลก - เวลาเชิงเส้นและการสงวนรักษาลำดับ *


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

หากคุณสามารถตกแต่งค่าของคุณในวิธีที่พวกเขา hashable ที่จะแก้ปัญหา ตัวอย่างเช่นกับพจนานุกรมแฟลตที่มีค่าเป็น hashable:

ys = {tuple(item.items()) for item in y}
[item for item in x if tuple(item.items()) not in ys]

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


หากรายการของคุณไม่ได้และไม่สามารถแฮชได้ แต่สิ่งเหล่านี้สามารถเทียบเคียงได้คุณจะได้รับเวลาบันทึกเชิงเส้นอย่างน้อย ( O(N*log M)ซึ่งดีกว่าO(N*M)เวลาแก้ไขรายการมาก แต่ไม่ดีเท่าO(N+M)เวลาของการแก้ปัญหาชุด) โดยการเรียงลำดับและการใช้bisect:

ys = sorted(y)
def bisect_contains(seq, item):
    index = bisect.bisect(seq, item)
    return index < len(seq) and seq[index] == item
[item for item in x if bisect_contains(ys, item)]

หากรายการของคุณไม่แฮชหรือเปรียบเทียบคุณก็ติดอยู่กับวิธีแก้ปัญหากำลังสอง


* โปรดทราบว่าคุณสามารถทำได้โดยใช้OrderedSetวัตถุคู่หนึ่งซึ่งคุณสามารถค้นหาสูตรอาหารและโมดูลของบุคคลที่สาม แต่ฉันคิดว่ามันง่ายกว่า

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


7

การค้นหาค่าในชุดนั้นเร็วกว่าการค้นหาในรายการ:

[item for item in x if item not in set(y)]

ฉันเชื่อว่านี่จะขยายขนาดได้ดีกว่า:

[item for item in x if item not in y]

ทั้งสองรักษาลำดับของรายการ


มันจะแคชset(y)และไม่แปลงyเป็นชุดใหม่ในแต่ละลูปหรือไม่ มิฉะนั้นคุณควรที่จะตอบสนองความต้องการของ ys = set(y); [i for i in x if i not in ys]abarnert:
Jacktose

2
การทดสอบคร่าวๆบางอย่างชี้ให้เห็นว่าif i not in set(y)ใช้เวลานานกว่า 25% if i not in y(รายการอยู่ที่ไหนy) การแปลงชุดล่วงหน้าใช้เวลาน้อยลง 55% ทดสอบด้วยความสั้นxและสวยyแต่ความแตกต่างควรมีความยาวมากกว่าถ้ามี
Jacktose

1
@Jacktose: ใช่การแก้ปัญหานี้ไม่ทำงานมากขึ้นเพราะมีการทำซ้ำและกัญชาทุกองค์ประกอบของyสำหรับทุกองค์ประกอบของx; item not in yเว้นแต่การเปรียบเทียบความเสมอภาคที่มีราคาแพงจริงๆเมื่อเทียบกับการคำนวณกัญชานี้มักจะสูญเสียธรรมดา
ShadowRanger

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

5

หากรายการอนุญาตให้ใช้องค์ประกอบที่ซ้ำกันคุณสามารถใช้ตัวนับจากคอลเลกชัน:

from collections import Counter
result = list((Counter(x)-Counter(y)).elements())

หากคุณต้องการรักษาลำดับขององค์ประกอบจาก x:

result = [ v for c in [Counter(y)] for v in x if not c[v] or c.subtract([v]) ]

นี่เป็นสิ่งที่ดีแม้ว่ามันจะเสียการสั่งซื้อ การแก้ไขที่มีความซับซ้อนมากขึ้นอีกนิด
ShadowRanger

@ShadowRanger มันแน่นอน แต่เพียงเล็กน้อย
Alain T.

ไม่รังเกียจฉันฉันแค่จะสั่นที่ลิสต์คอมแพคด้วยแคชและผลข้างเคียง (แม้ว่าฉันคิดว่าการรวมกันของทั้งสองจะกำจัดผลข้างเคียงที่มองเห็นได้จากภายนอก?) :-)
ShadowRanger

นอกจากนี้รหัสนี้จะไม่ทำงานตามที่เขียนไว้ Counter.subtractไม่ได้ลบองค์ประกอบที่มีค่าเป็นศูนย์ ( -และ-=ทำ แต่ไม่รวมsubtract) ดังนั้นคุณจะไม่หยุดลบองค์ประกอบ คุณต้องการแทนที่not v in cด้วยnot c[v](ซึ่งคืนค่าศูนย์สำหรับองค์ประกอบที่ไม่มีอยู่ดังนั้นคุณสามารถทดสอบการส่งคืนสำหรับ "zeroiness" ผ่านได้อย่างปลอดภัยnot)
ShadowRanger

@ ShadowRanger จับได้ดี! แก้ไขทันที
Alain T.

3

วิธีแก้ไขปัญหาอื่นมีปัญหาหนึ่งในสองสามปัญหา:

  1. พวกเขาไม่รักษาความสงบเรียบร้อยหรือ
  2. พวกเขาจะไม่ลบจำนวนองค์ประกอบที่แม่นยำเช่นx = [1, 2, 2, 2]และy = [2, 2]เปลี่ยนyเป็นsetและลบองค์ประกอบที่ตรงกันทั้งหมด (ออก[1]อย่างเดียว) หรือลบองค์ประกอบที่ไม่ซ้ำกันหนึ่งรายการ (ออก[1, 2, 2]) เมื่อพฤติกรรมที่เหมาะสมจะเป็นการลบ2สองครั้ง ออกจาก[1, 2]หรือ
  3. พวกเขาO(m * n)ทำงานได้ซึ่งเป็นทางออกที่ดีที่สุดที่สามารถO(m + n)ทำงานได้

Alain อยู่ในเส้นทางที่ถูกต้องพร้อมที่Counterจะแก้ปัญหา # 2 และ # 3 แต่โซลูชันนั้นจะสูญเสียการสั่งซื้อ วิธีแก้ปัญหาที่รักษาลำดับ (การลบnสำเนาแรกของแต่ละค่าสำหรับการnทำซ้ำในlistของค่าที่จะลบ) คือ:

from collections import Counter

x = [1,2,3,4,3,2,1]  
y = [1,2,2]  
remaining = Counter(y)

out = []
for val in x:
    if remaining[val]:
        remaining[val] -= 1
    else:
        out.append(val)
# out is now [3, 4, 3, 1], having removed the first 1 and both 2s.

ลองออนไลน์!

หากต้องการให้ลบสำเนาสุดท้ายของแต่ละองค์ประกอบเพียงเปลี่ยนforลูปเป็นfor val in reversed(x):และเพิ่มout.reverse()ทันทีหลังจากออกจากforลูป

สร้างCounterเป็นO(n)ในแง่ของy'ความยาว S, iterating xเป็นO(n)ในแง่ของx' s ความยาวและCounterการทดสอบการเป็นสมาชิกและการกลายพันธุ์ที่มีO(1)ในขณะที่list.appendถูกตัดจ่ายO(1)(ที่กำหนดappendสามารถO(n)แต่สำหรับหลายappends ที่ใหญ่-O โดยรวมเฉลี่ยO(1)ตั้งแต่น้อยลงและน้อย ของพวกเขาต้องจัดสรรก) O(m + n)เพื่อให้การทำงานโดยรวมทำคือ

นอกจากนี้คุณยังสามารถทดสอบหาว่ามีองค์ประกอบใด ๆyที่ไม่ได้ถูกลบออกจากxการทดสอบหรือไม่:

remaining = +remaining  # Removes all keys with zero counts from Counter
if remaining:
    # remaining contained elements with non-zero counts

หมายเหตุ: ไม่จำเป็นต้องมีค่าที่จะ hashable แต่วิธีการแก้ปัญหาใด ๆ ที่ไม่ต้องใช้วัตถุ hashable อย่างใดอย่างหนึ่งไม่ได้อเนกประสงค์ (เช่นสามารถนับints เข้าแถวยาวคงที่) หรือมีการทำมากขึ้นกว่าO(m + n)การทำงาน (เช่นขนาดใหญ่ที่ดีที่สุดต่อไป - O จะทำการเรียงลำดับlistของคู่ค่า / การนับที่ไม่ซ้ำกันการเปลี่ยนการO(1) dictค้นหาเป็นการO(log n)ค้นหาแบบไบนารีคุณต้องมีค่าที่ไม่ซ้ำกับการนับของพวกเขาไม่ใช่แค่เรียงค่าที่ไม่ซ้ำกันเพราะมิฉะนั้นคุณจะต้องจ่ายO(n)ค่าใช้จ่ายเพื่อลบ องค์ประกอบจากการเรียงลำดับlist)
ShadowRanger

2

ลองสิ่งนี้

def subtract_lists(a, b):
    """ Subtracts two lists. Throws ValueError if b contains items not in a """
    # Terminate if b is empty, otherwise remove b[0] from a and recurse
    return a if len(b) == 0 else [a[:i] + subtract_lists(a[i+1:], b[1:]) 
                                  for i in [a.index(b[0])]][0]

>>> x = [1,2,3,4,5,6,7,8,9,0]
>>> y = [1,3,5,7,9]
>>> subtract_lists(x,y)
[2, 4, 6, 8, 0]
>>> x = [1,2,3,4,5,6,7,8,9,0,9]
>>> subtract_lists(x,y)
[2, 4, 6, 8, 0, 9]     #9 is only deleted once
>>>

2

ฉันคิดว่าวิธีที่ง่ายที่สุดในการบรรลุเป้าหมายนี้คือการใช้ set ()

>>> x = [1,2,3,4,5,6,7,8,9,0]  
>>> y = [1,3,5,7,9]  
>>> list(set(x)- set(y))
[0, 2, 4, 6, 8]

1

คำตอบที่ได้รับจาก @aaronasterling ดูดี แต่มันไม่เข้ากันกับอินเตอร์เฟซที่เริ่มต้นของรายการ: VSx = MyList(1, 2, 3, 4) x = MyList([1, 2, 3, 4])ดังนั้นรหัสด้านล่างสามารถใช้เป็นรายการที่เป็นมิตรกับงูหลาม:

class MyList(list):
    def __init__(self, *args):
        super(MyList, self).__init__(*args)

    def __sub__(self, other):
        return self.__class__([item for item in self if item not in other])

ตัวอย่าง:

x = MyList([1, 2, 3, 4])
y = MyList([2, 5, 2])
z = x - y

0

ฉันคิดว่านี่เร็วกว่า:

In [1]: a = [1,2,3,4,5]

In [2]: b = [2,3,4,5]

In [3]: c = set(a) ^ set(b)

In [4]: c
Out[4]: {1}

นี่ไม่ใช่การลบ อันที่จริงนี่คือความแตกต่างสมมาตรระหว่างสองรายการ
Parth Chauhan

นอกจากนี้ยังใช้งานได้เฉพาะกับวัตถุที่ลบได้ในรายการ
zhukovgreen

-1

ตัวอย่างนี้ลบสองรายการ:

# List of pairs of points
list = []
list.append([(602, 336), (624, 365)])
list.append([(635, 336), (654, 365)])
list.append([(642, 342), (648, 358)])
list.append([(644, 344), (646, 356)])
list.append([(653, 337), (671, 365)])
list.append([(728, 13), (739, 32)])
list.append([(756, 59), (767, 79)])

itens_to_remove = []
itens_to_remove.append([(642, 342), (648, 358)])
itens_to_remove.append([(644, 344), (646, 356)])

print("Initial List Size: ", len(list))

for a in itens_to_remove:
    for b in list:
        if a == b :
            list.remove(b)

print("Final List Size: ", len(list))

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