ลบองค์ประกอบทั้งหมดที่เกิดขึ้นในรายการหนึ่งจากอีกรายการหนึ่ง


365

สมมติว่าผมมีสองรายการและl1 l2ฉันต้องการที่จะดำเนินการl1 - l2ซึ่งจะส่งกลับทุกองค์ประกอบของไม่ได้อยู่ในl1l2

ฉันสามารถคิดวิธีไร้เดียงสาห่วงในการทำเช่นนี้ แต่ที่จะไม่มีประสิทธิภาพจริงๆ อะไรคือวิธีการทำสิ่งที่มีประสิทธิภาพและมีประสิทธิภาพ

ตัวอย่างเช่นถ้ามีl1 = [1,2,6,8] and l2 = [2,3,5,8], l1 - l2ควรกลับ[1,6]


12
เพียงแค่เคล็ดลับ: PEP8ระบุว่าตัวพิมพ์เล็ก "L" ไม่ควรใช้เพราะมันดูมากเกินไปเช่น 1.
spelchekr

2
ฉันเห็นด้วย. ฉันอ่านคำถามทั้งหมดนี้และคำตอบที่สงสัยว่าทำไมคนยังคงใช้สิบเอ็ดและสิบสอง มันก็ต่อเมื่อฉันอ่านความคิดเห็นของ @spelchekr ว่ามันสมเหตุสมผลแล้ว
robline


@JimG Dataframe และรายการไม่เหมือนกัน
ลดกิจกรรม

คำตอบ:


491

Python มีคุณสมบัติภาษาที่เรียกว่าList Comprehensionsซึ่งเหมาะอย่างยิ่งที่จะทำให้สิ่งนี้ง่ายมาก คำสั่งต่อไปนี้ทำสิ่งที่คุณต้องการและเก็บผลลัพธ์ไว้ในl3:

l3 = [x for x in l1 if x not in l2]

l3[1, 6]จะมี


8
ไพเราะมาก ฉันชอบมัน! มันมีประสิทธิภาพแค่ไหน?
fandom

2
ฉันเชื่อว่ามีประสิทธิภาพมากและมีประโยชน์ในการอ่านอย่างชัดเจนและชัดเจนว่าคุณกำลังทำอะไรให้สำเร็จ ฉันเจอบล็อกโพสต์คุณอาจพบว่าน่าสนใจเกี่ยวกับประสิทธิภาพ: blog.cdleary.com/2010/04/efficiency-of-list-comprehensions
Donut

6
@ สุ่ม: รายการความเข้าใจตัวเองค่อนข้างมีประสิทธิภาพ (แม้ว่าความเข้าใจกำเนิดอาจจะมีประสิทธิภาพมากขึ้นโดยไม่ทำซ้ำองค์ประกอบในหน่วยความจำ) แต่inผู้ประกอบการที่ไม่ได้มีประสิทธิภาพในรายการ inในรายการคือ O (n) ในขณะที่inในชุดคือ O (1) อย่างไรก็ตามจนกว่าคุณจะไปถึงองค์ประกอบหลายพันชิ้นหรือมากกว่านั้นคุณไม่น่าสังเกตความแตกต่าง
Daniel Pryden

1
l3 = [x for x in l1 if x not in set(l2)]? ฉันแน่ใจว่าset(l2)จะถูกเรียกมากกว่าหนึ่งครั้ง
Danosaure

5
คุณยังสามารถเพียงแค่ตั้งแล้วบอกว่าl2s = set(l2) l3 = [x for x in l1 if x not in l2s]ง่ายขึ้นเล็กน้อย
spelchekr

149

วิธีหนึ่งคือการใช้ชุด:

>>> set([1,2,6,8]) - set([2,3,5,8])
set([1, 6])

58
นี่จะเป็นการลบรายการที่ซ้ำกันออกจากl1ซึ่งอาจเป็นผลข้างเคียงที่ไม่พึงประสงค์
kindall

37
.. และสูญเสียคำสั่งองค์ประกอบ (ถ้าคำสั่งเป็นสิ่งสำคัญ)
Danosaure

3
ผมแค่อยากจะเพิ่มที่ฉันหมดเวลานี้เทียบกับคำตอบที่ได้รับการยอมรับและมันก็เป็น performant มากขึ้นโดยปัจจัยที่เกี่ยวกับ timeit.timeit('a = [1,2,3,4]; b = [1,3]; c = [i for i in a if a not in b]', number=100000) -> 0.12061533199999985 timeit.timeit('a = {1,2,3,4}; b = {1,3}; c = a - b', number=100000) -> 0.041062259999989693: ดังนั้นหากประสิทธิภาพเป็นปัจจัยสำคัญคำตอบนี้อาจเหมาะสมกว่า (และหากคุณไม่สนใจเรื่องซ้ำซ้อนหรือคำสั่งซื้อ)
14154

37

คุณอาจใช้filterกับนิพจน์แลมบ์ดาเพื่อรับผลลัพธ์ที่ต้องการ ตัวอย่างเช่น:

>>> l1 = [1,2,6,8]
>>> l2 = set([2,3,5,8])

#     v  `filter` returns the a iterator object. Here I'm type-casting 
#     v  it to `list` in order to display the resultant value
>>> list(filter(lambda x: x not in l2, l1))
[1, 6]

การเปรียบเทียบประสิทธิภาพ

ที่นี่ฉันกำลังเปรียบเทียบประสิทธิภาพของคำตอบทั้งหมดที่กล่าวถึงที่นี่ ตามที่คาดไว้การดำเนินการของ Arkku setนั้นเร็วที่สุด

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


32

การขยายคำตอบของ Donut และคำตอบอื่น ๆ ที่นี่คุณสามารถรับผลลัพธ์ที่ดียิ่งขึ้นโดยใช้ generator comprehension แทน list comprehension และใช้setโครงสร้างข้อมูล (เนื่องจากinโอเปอเรเตอร์คือ O (n) ในรายการ แต่ O (1) ในชุด)

ดังนั้นนี่คือฟังก์ชั่นที่เหมาะกับคุณ:

def filter_list(full_list, excludes):
    s = set(excludes)
    return (x for x in full_list if x not in s)

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

filtered_list = list(filter_list(full_list, excludes))

29

ใช้ประเภทชุด Python นั่นจะเป็น Pythonic ที่สุด :)

นอกจากนี้เนื่องจากเป็นแบบดั้งเดิมจึงควรเป็นวิธีที่เหมาะสมที่สุดด้วย

ดู:

http://docs.python.org/library/stdtypes.html#set

http://docs.python.org/library/sets.htm (สำหรับไพ ธ อนเก่า)

# Using Python 2.7 set literal format.
# Otherwise, use: l1 = set([1,2,6,8])
#
l1 = {1,2,6,8}
l2 = {2,3,5,8}
l3 = l1 - l2

5
เมื่อใช้ชุดควรสังเกตว่าผลลัพธ์ของการสั่งซื้อคือ {1,3,2} กลายเป็น {1,2,3} และ {"A", "C", "B"} กลายเป็น {"A", "B", "C"} และคุณอาจไม่ต้องการมัน
Pablo Reyes

2
วิธีนี้จะไม่ทำงานหากรายการl1มีองค์ประกอบที่ซ้ำกัน
jdhao

10

ใช้ Set Comprehensions {x สำหรับ x ใน l2} หรือ set (l2) เพื่อรับ set จากนั้นใช้List Comprehensionsเพื่อรับ list

l2set = set(l2)
l3 = [x for x in l1 if x not in l2set]

รหัสทดสอบมาตรฐาน:

import time

l1 = list(range(1000*10 * 3))
l2 = list(range(1000*10 * 2))

l2set = {x for x in l2}

tic = time.time()
l3 = [x for x in l1 if x not in l2set]
toc = time.time()
diffset = toc-tic
print(diffset)

tic = time.time()
l3 = [x for x in l1 if x not in l2]
toc = time.time()
difflist = toc-tic
print(difflist)

print("speedup %fx"%(difflist/diffset))

ผลการทดสอบเกณฑ์มาตรฐาน:

0.0015058517456054688
3.968189239501953
speedup 2635.179227x    

1
l2set = set( l2 )แทนl2set = { x for x in l2 }
cz

1
soultion ดี! แต่มันจะต้องถูกเก็บไว้ในใจว่ามันจะใช้ได้เฉพาะกับวัตถุที่แฮช
Eerik Sven Puudist

7

ทางเลือกอื่น:

reduce(lambda x,y : filter(lambda z: z!=y,x) ,[2,3,5,8],[1,2,6,8])

2
มีข้อได้เปรียบในการใช้วิธีนี้หรือไม่? ดูเหมือนว่ามันจะซับซ้อนและอ่านยากขึ้นโดยไม่มีประโยชน์อะไรมากมาย
skrrgwasme

นั่นอาจดูซับซ้อน การลดมีความยืดหยุ่นสูงและสามารถใช้งานได้หลายอย่าง เรียกได้ว่าเป็นรอยพับ ลดเป็นจริง foldl สมมติว่าคุณต้องการเพิ่มเนื้อหาที่ซับซ้อนมากขึ้นในนั้นจากนั้นมันจะเป็นไปได้ในฟังก์ชั่นนี้ แต่รายการความเข้าใจซึ่งเป็นคำตอบที่ดีที่สุดที่เลือกจะได้รับผลลัพธ์ของรายการประเภทเดียวกันเช่นและอาจมีความยาวเท่ากัน เปลี่ยนประเภทเอาต์พุตเช่นกัน en.wikipedia.org/wiki/Fold_%28higher-order_function%29 โซลูชันนี้มีความซับซ้อนน้อยกว่า คนอื่นอาจจะใช่หรือไม่ก็ได้
Akshay Hazari

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