ทดสอบว่ารายการใช้รายการร่วมกันใน python หรือไม่


132

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

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

In [79]: b = [8, 7, 6]

In [80]: c = [8, 7, 6, 5]

In [81]: def lists_overlap(a, b):
   ....:     for i in a:
   ....:         if i in b:
   ....:             return True
   ....:     return False
   ....: 

In [82]: lists_overlap(a, b)
Out[82]: False

In [83]: lists_overlap(a, c)
Out[83]: True

In [84]: def lists_overlap2(a, b):
   ....:     return len(set(a).intersection(set(b))) > 0
   ....: 

การเพิ่มประสิทธิภาพเพียงอย่างเดียวที่ฉันคิดได้คือการลดลงlen(...) > 0เนื่องจากbool(set([]))ให้ผลเป็นเท็จ และแน่นอนว่าถ้าคุณเก็บรายการของคุณไว้เป็นชุดเพื่อเริ่มต้นคุณจะบันทึกค่าใช้จ่ายในการสร้างชุด
msw


1
โปรดทราบว่าคุณไม่สามารถที่แตกต่างกันTrueจาก1และจากFalse ได้รับเช่นเดียวกันกับโซลูชันอื่น ๆ 0not set([1]).isdisjoint([True])True
Dimali

คำตอบ:


315

คำตอบสั้น ๆ : ใช้not set(a).isdisjoint(b)โดยทั่วไปจะเร็วที่สุด

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

bool(set(a) & set(b))

เพราะชุดจะถูกเก็บไว้โดยใช้ตารางแฮชในหลามค้นหาพวกเขาO(1) (ดูที่นี่สำหรับข้อมูลเพิ่มเติมเกี่ยวกับความซับซ้อนของผู้ประกอบการในหลาม) ตามทฤษฎีแล้วค่านี้เป็นO(n+m)ค่าเฉลี่ยสำหรับnและmวัตถุในรายการaและb. แต่ 1) จะต้องสร้างชุดจากรายการก่อนซึ่งอาจใช้เวลาไม่มากและ 2) สมมติว่าการชนกันของแฮชจะเบาบางลงในข้อมูลของคุณ

วิธีที่สองในการทำคือการใช้นิพจน์ตัวสร้างที่ดำเนินการวนซ้ำในรายการเช่น:

any(i in a for i in b)

สิ่งนี้ช่วยให้สามารถค้นหาในสถานที่ได้ดังนั้นจึงไม่มีการจัดสรรหน่วยความจำใหม่สำหรับตัวแปรตัวกลาง นอกจากนี้ยังประกันตัวในการพบครั้งแรก แต่ตัวinดำเนินการจะอยู่O(n)ในรายการเสมอ (ดูที่นี่ )

อีกทางเลือกหนึ่งที่เสนอคือไฮบริดเพื่อวนซ้ำผ่านรายการใดรายการหนึ่งแปลงอีกรายการในชุดและทดสอบการเป็นสมาชิกในชุดนี้ดังนี้:

a = set(a); any(i in a for i in b)

แนวทางที่สี่คือการใช้ประโยชน์จากisdisjoint()วิธีการของเซต (แช่แข็ง) (ดูที่นี่ ) ตัวอย่างเช่น:

not set(a).isdisjoint(b)

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

from timeit import timeit
>>> timeit('bool(set(a) & set(b))', setup="a=list(range(1000));b=list(range(1000))", number=100000)
26.077727576019242
>>> timeit('any(i in a for i in b)', setup="a=list(range(1000));b=list(range(1000))", number=100000)
0.16220548999262974

นี่คือกราฟของเวลาดำเนินการสำหรับตัวอย่างนี้ในฟังก์ชันของขนาดรายการ:

เวลาดำเนินการทดสอบการแชร์องค์ประกอบเมื่อแชร์ที่จุดเริ่มต้น

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

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

>>> timeit('any(i in a for i in b)', setup="a=list(range(1000));b=[x+998 for x in range(999,0,-1)]", number=1000))
13.739536046981812
>>> timeit('bool(set(a) & set(b))', setup="a=list(range(1000));b=[x+998 for x in range(999,0,-1)]", number=1000))
0.08102107048034668

เวลาดำเนินการทดสอบการแชร์องค์ประกอบเมื่อแชร์ในตอนท้าย

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

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

เวลาดำเนินการทดสอบการแชร์องค์ประกอบสำหรับข้อมูลที่สร้างแบบสุ่มและมีโอกาสแชร์สูง เวลาดำเนินการทดสอบการแชร์องค์ประกอบสำหรับข้อมูลที่สร้างแบบสุ่มและมีโอกาสแชร์สูง

โอกาสสูงของการแบ่งปัน: [1, 2*len(a)]องค์ประกอบจะได้รับการสุ่มจาก โอกาสที่ต่ำของการแบ่งปัน: [1, 1000*len(a)]องค์ประกอบจะได้รับการสุ่มจาก

ถึงตอนนี้การวิเคราะห์นี้คาดว่าทั้งสองรายการมีขนาดเท่ากัน ในกรณีของสองรายการที่มีขนาดต่างกันเช่นaมีขนาดเล็กกว่ามากisdisjoint()จะเร็วกว่าเสมอ:

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

ตรวจสอบให้แน่ใจว่าaรายการมีขนาดเล็กลงมิฉะนั้นประสิทธิภาพจะลดลง ในการทดลองนี้ไฟล์aขนาดรายการถูกตั้งค่าคงที่5เป็น

สรุป:

  • หากรายการมีขนาดเล็กมาก (<10 องค์ประกอบ) not set(a).isdisjoint(b)จะเร็วที่สุดเสมอ
  • หากองค์ประกอบในรายการถูกจัดเรียงหรือมีโครงสร้างปกติที่คุณสามารถใช้ประโยชน์จากนิพจน์ตัวสร้าง any(i in a for i in b)จะเร็วที่สุดสำหรับรายการขนาดใหญ่
  • ทดสอบจุดตัดกับ not set(a).isdisjoint(b)ซึ่งเร็วกว่าbool(set(a) & set(b))เสมอ
  • ไฮบริด "วนซ้ำผ่านรายการทดสอบในชุด" a = set(a); any(i in a for i in b)โดยทั่วไปจะช้ากว่าวิธีอื่น ๆ
  • นิพจน์ของเครื่องกำเนิดไฟฟ้าและไฮบริดจะช้ากว่าวิธีอื่น ๆ มากเมื่อพูดถึงรายการโดยไม่ต้องแชร์องค์ประกอบ

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


8
นั่นคือข้อมูลที่มีประโยชน์บางอย่างที่นั่นแสดงให้เห็นว่าการวิเคราะห์แบบ Big-O ไม่ใช่เพียงแค่ทั้งหมดและสิ้นสุดการให้เหตุผลทั้งหมดเกี่ยวกับเวลาทำงาน
Steve Allison

แล้วสถานการณ์ที่เลวร้ายที่สุดล่ะ? anyออกที่ค่าแรกที่ไม่ใช่เท็จ เมื่อใช้รายการที่มีค่าที่ตรงกันเพียงอย่างเดียวเราจะได้สิ่งนี้: timeit('any(i in a for i in b)', setup="a=list(range(1000));b=[x+998 for x in range(999,-0,-1)]", number=1000) 13.739536046981812 timeit('bool(set(a) & set(b))', setup="a=list(range(1000));b=[x+998 for x in range(999,-0,-1)]", number=1000) 0.08102107048034668 ... และมีการทำซ้ำ 1,000 ครั้งเท่านั้น
RobM

2
ขอบคุณ @RobM สำหรับข้อมูล ฉันได้อัปเดตคำตอบของฉันเพื่อแสดงถึงสิ่งนี้และคำนึงถึงเทคนิคอื่น ๆ ที่เสนอในชุดข้อความนี้
Soravux

ควรnot set(a).isdisjoint(b)ทดสอบว่าสองรายการมีสมาชิกร่วมกันหรือไม่ set(a).isdisjoint(b)จะคืนค่าTrueหากทั้งสองรายการไม่มีสมาชิกร่วมกัน คำตอบควรแก้ไข?
Guillochon

1
ขอบคุณสำหรับการแจ้งเตือน @Guillochon ได้รับการแก้ไขแล้ว
Soravux

25
def lists_overlap3(a, b):
    return bool(set(a) & set(b))

หมายเหตุ: ข้างต้นถือว่าคุณต้องการบูลีนเป็นคำตอบ หากสิ่งที่คุณต้องการคือนิพจน์ที่จะใช้ในifคำสั่งเพียงแค่ใช้if set(a) & set(b):


5
นี่เป็นกรณีที่เลวร้ายที่สุด O (n + m) อย่างไรก็ตามข้อเสียคือมันสร้างชุดใหม่และไม่ได้ประกันตัวออกมาเมื่อพบองค์ประกอบทั่วไปในช่วงต้น
Matthew Flaschen

1
O(n + m)ผมอยากรู้ว่าทำไมนี้ ฉันเดาว่าชุดถูกนำไปใช้โดยใช้ตารางแฮชและทำให้ตัวinดำเนินการสามารถทำงานได้O(1)ทันเวลา (ยกเว้นในกรณีที่เสื่อมสภาพ) ถูกต้องหรือไม่ ถ้าเป็นเช่นนั้นเนื่องจากตารางแฮชมีประสิทธิภาพการค้นหาเคสที่แย่ที่สุดO(n)นั่นหมายความว่าในกรณีที่แย่กว่านั้นจะมีO(n * m)ประสิทธิภาพหรือไม่?
fmark

1
@fmark: ในทางทฤษฎีคุณพูดถูก ในทางปฏิบัติไม่มีใครสนใจ อ่านความคิดเห็นใน Objects / dictobject.c ในซอร์ส CPython (ชุดเป็นเพียงคำสั่งที่มีคีย์เท่านั้นไม่มีค่า) และดูว่าคุณสามารถสร้างรายการคีย์ที่จะทำให้ประสิทธิภาพการค้นหา O (n) ได้หรือไม่
John Machin

โอเคขอบคุณสำหรับการชี้แจงฉันสงสัยว่ามีเวทมนตร์เกิดขึ้นไหม :) แม้ว่าฉันยอมรับว่าในทางปฏิบัติฉันไม่จำเป็นต้องสนใจ แต่การสร้างรายการคีย์ที่จะทำให้เกิดO(n)ประสิทธิภาพในการค้นหานั้นเป็นเรื่องเล็กน้อย) โปรดดูpastebin.com/Kn3kAW7uสำหรับ lafs
fmark

2
ใช่ฉันรู้. นอกจากนี้ฉันเพิ่งอ่านแหล่งที่มาที่คุณชี้ให้ฉันดูซึ่งมีเอกสารเกี่ยวกับเวทมนตร์มากกว่าในกรณีของฟังก์ชันแฮชที่ไม่สุ่ม (เช่นในตัว) ฉันคิดว่ามันต้องใช้การสุ่มเหมือน Java ซึ่งส่งผลให้เกิดความชั่วร้ายเช่นนี้stackoverflow.com/questions/2634690/… stackoverflow.com/questions/2634690/...ฉันต้องเตือนตัวเองอยู่เสมอว่าPython ไม่ใช่ Java (ขอบคุณพระเจ้า!)
fmark

10
def lists_overlap(a, b):
  sb = set(b)
  return any(el in sb for el in a)

นี่เป็นวิธีที่เหมาะสมที่สุดโดยไม่มีอาการ (กรณีที่เลวร้ายที่สุด O (n + m)) และอาจดีกว่าวิธีการตัดกันเนื่องจากการanyลัดวงจร

เช่น:

lists_overlap([3,4,5], [1,2,3])

จะส่งคืน True ทันทีที่ไปถึง 3 in sb

แก้ไข: รูปแบบอื่น (ขอบคุณ Dave Kirby):

def lists_overlap(a, b):
  sb = set(b)
  return any(itertools.imap(sb.__contains__, a))

สิ่งนี้อาศัยตัวimapวนซ้ำของซึ่งถูกนำมาใช้ใน C แทนที่จะเป็นตัวสร้างความเข้าใจ นอกจากนี้ยังใช้sb.__contains__เป็นฟังก์ชันการทำแผนที่ ฉันไม่รู้ว่าสิ่งนี้ทำให้ประสิทธิภาพแตกต่างกันมากแค่ไหน มันจะยังคงลัดวงจร


1
ลูปในแนวทางการตัดกันทั้งหมดอยู่ในรหัส C มีลูปหนึ่งในแนวทางของคุณที่มีโค้ด Python สิ่งที่ไม่ทราบแน่ชัดคือทางแยกที่ว่างเปล่ามีโอกาสหรือไม่น่าเป็นไปได้
John Machin

2
คุณยังสามารถใช้any(itertools.imap(sb.__contains__, a))ซึ่งควรจะเร็วกว่าเนื่องจากหลีกเลี่ยงการใช้ฟังก์ชันแลมบ์ดา
Dave Kirby

ขอบคุณ @Dave :) ฉันเห็นด้วยกับการลบแลมด้าเป็นสิ่งที่ชนะ
Matthew Flaschen

4

คุณยังสามารถใช้anyกับการทำความเข้าใจรายการ:

any([item in a for item in b])

6
คุณทำได้ แต่เวลาคือ O (n * m) ในขณะที่เวลาสำหรับแนวทางจุดตัดที่กำหนดคือ O (n + m) คุณสามารถทำได้โดยไม่ต้องเข้าใจรายการ (เสีย[]) และจะทำงานได้เร็วขึ้นและใช้หน่วยความจำน้อยลง แต่เวลาจะยังคงเป็น O (n * m)
John Machin

1
แม้ว่าการวิเคราะห์ O ขนาดใหญ่ของคุณจะเป็นเรื่องจริง แต่ฉันสงสัยว่าสำหรับค่า n และ m เพียงเล็กน้อยเวลาที่ใช้ในการสร้างแฮชแท็กที่สำคัญจะเข้ามามีบทบาท Big O ไม่สนใจเวลาที่ใช้ในการคำนวณแฮช
Anthony Conyers

2
การสร้าง "แฮชแท็ก" ถูกตัดจำหน่าย O (n)
John Machin

1
ฉันเข้าใจแล้ว แต่ค่าคงที่ที่คุณทิ้งไปนั้นค่อนข้างใหญ่ ไม่สำคัญสำหรับค่า n จำนวนมาก แต่สำหรับค่าขนาดเล็ก
Anthony Conyers

3

ใน python 2.6 หรือใหม่กว่าคุณสามารถทำได้:

return not frozenset(a).isdisjoint(frozenset(b))

1
ดูเหมือนว่าเราไม่จำเป็นต้องจัดหา set หรือ frozenset เป็นอาร์กิวเมนต์แรก ฉันลองใช้สตริงและมันใช้งานได้ (เช่น: ทำซ้ำได้ทุกอย่าง)
Aktau

2

คุณสามารถใช้นิพจน์ตัวสร้างฟังก์ชัน / wa ในตัว:

def list_overlap(a,b): 
     return any(i for i in a if i in b)

ดังที่ John and Lie ได้ชี้ให้เห็นว่าสิ่งนี้ให้ผลลัพธ์ที่ไม่ถูกต้องเมื่อทุก ๆ รายการที่ฉันแชร์โดยทั้งสองรายการ bool (i) == False มันควรจะเป็น:

return any(i in b for i in a)

1
การขยายความคิดเห็นของ Lie Ryan: จะให้ผลลัพธ์ที่ไม่ถูกต้องสำหรับรายการใด ๆ x ที่อยู่ในจุดตัดที่bool(x)เป็น False ในตัวอย่างโกหกไรอัน x เป็น 0 การแก้ไขเพียงอย่างเดียวคือที่เขียนดีในฐานะเห็นแล้วany(True for i in a if i in b) any(i in b for i in a)
John Machin

1
การแก้ไข: จะให้ผลผิดเมื่อทุกรายการxในสี่แยกดังกล่าวว่าเป็นbool(x) False
John Machin

1

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

กรณีที่แย่ที่สุดสำหรับรายการ:

>>> timeit('bool(set(a) & set(b))',  setup="a=list(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)
100.91506409645081
>>> timeit('any(i in a for i in b)', setup="a=list(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)
19.746716022491455
>>> timeit('any(i in a for i in b)', setup="a= set(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)
0.092626094818115234

และกรณีที่ดีที่สุดสำหรับรายการ:

>>> timeit('bool(set(a) & set(b))',  setup="a=list(range(10000)); b=list(range(10000))", number=100000)
154.69790101051331
>>> timeit('any(i in a for i in b)', setup="a=list(range(10000)); b=list(range(10000))", number=100000)
0.082653045654296875
>>> timeit('any(i in a for i in b)', setup="a= set(range(10000)); b=list(range(10000))", number=100000)
0.08434605598449707

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

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


1
ใช้isdisjoint()วิธีการตั้งค่า (แช่แข็ง) ตามที่ระบุโดย @Toughy จะดีกว่า: timeit('any(i in a for i in b)', setup="a= set(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)=> 0.00913715362548828
Aktau

1

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

len(set(a+b+c))==len(a+b+c) ส่งคืน True หากไม่มีการทับซ้อนกัน


หากค่าแรกทับซ้อนกันค่านี้จะยังคงแปลงรายการทั้งหมดเป็นชุดไม่ว่าจะใหญ่แค่ไหนก็ตาม
Peter Wood

1

ฉันจะโยนอีกอันหนึ่งด้วยรูปแบบการเขียนโปรแกรมที่ใช้งานได้:

any(map(lambda x: x in a, b))

คำอธิบาย:

map(lambda x: x in a, b)

กลับรายการของ booleans ที่องค์ประกอบของที่พบในb aรายการที่ถูกส่งผ่านไปแล้วanyซึ่งก็จะส่งกลับTrueถ้าองค์ประกอบใด True

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