คำตอบสั้น ๆ : ใช้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()
วิธีนี้เป็นแนวทางที่ดีที่สุดเนื่องจากนิพจน์ตัวสร้างจะใช้เวลาดำเนินการนานกว่ามากเนื่องจากไม่มีประสิทธิภาพมากเมื่อไม่มีการแชร์องค์ประกอบ
len(...) > 0
เนื่องจากbool(set([]))
ให้ผลเป็นเท็จ และแน่นอนว่าถ้าคุณเก็บรายการของคุณไว้เป็นชุดเพื่อเริ่มต้นคุณจะบันทึกค่าใช้จ่ายในการสร้างชุด