Python สามารถทดสอบความเป็นสมาชิกของหลายค่าในรายการได้หรือไม่?


122

ฉันต้องการทดสอบว่าค่าสองค่าขึ้นไปมีสมาชิกในรายการหรือไม่ แต่ฉันได้รับผลลัพธ์ที่ไม่คาดคิด:

>>> 'a','b' in ['b', 'a', 'foo', 'bar']
('a', True)

ดังนั้น Python สามารถทดสอบการเป็นสมาชิกของหลายค่าพร้อมกันในรายการได้หรือไม่? ผลลัพธ์นั้นหมายความว่าอย่างไร?

คำตอบ:


198

สิ่งนี้ทำในสิ่งที่คุณต้องการและจะได้ผลในเกือบทุกกรณี:

>>> all(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])
True

นิพจน์'a','b' in ['b', 'a', 'foo', 'bar']ไม่ทำงานตามที่คาดไว้เนื่องจาก Python ตีความว่าเป็นทูเพิล:

>>> 'a', 'b'
('a', 'b')
>>> 'a', 5 + 2
('a', 7)
>>> 'a', 'x' in 'xerxes'
('a', True)

ตัวเลือกอื่น

มีวิธีอื่นในการดำเนินการทดสอบนี้ แต่จะใช้ไม่ได้กับอินพุตประเภทต่างๆ ดังที่Kabieชี้ให้เห็นคุณสามารถแก้ปัญหานี้ได้โดยใช้ชุด ...

>>> set(['a', 'b']).issubset(set(['a', 'b', 'foo', 'bar']))
True
>>> {'a', 'b'} <= {'a', 'b', 'foo', 'bar'}
True

... บางครั้ง:

>>> {'a', ['b']} <= {'a', ['b'], 'foo', 'bar'}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

ชุดสามารถสร้างได้ด้วยองค์ประกอบที่แฮชได้เท่านั้น แต่นิพจน์ของเครื่องกำเนิดไฟฟ้าall(x in container for x in items)สามารถรองรับคอนเทนเนอร์ได้เกือบทุกประเภท ข้อกำหนดเดียวคือcontainerสามารถทำซ้ำได้ (เช่นไม่ใช่เครื่องกำเนิดไฟฟ้า) itemsสามารถทำซ้ำได้เลย

>>> container = [['b'], 'a', 'foo', 'bar']
>>> items = (i for i in ('a', ['b']))
>>> all(x in [['b'], 'a', 'foo', 'bar'] for x in items)
True

การทดสอบความเร็ว

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

ต่อไปนี้คือเกณฑ์มาตรฐานบางประการสำหรับภาพประกอบ ความแตกต่างที่ใหญ่ที่สุดเกิดขึ้นเมื่อทั้งคู่containerและitemsค่อนข้างเล็ก ในกรณีนั้นแนวทางย่อยจะเกี่ยวกับลำดับความสำคัญที่เร็วกว่า:

>>> smallset = set(range(10))
>>> smallsubset = set(range(5))
>>> %timeit smallset >= smallsubset
110 ns ± 0.702 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
>>> %timeit all(x in smallset for x in smallsubset)
951 ns ± 11.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

นี่ดูเหมือนความแตกต่างที่ยิ่งใหญ่ แต่ตราบใดที่containerเป็นชุดallก็ยังสามารถใช้งานได้อย่างสมบูรณ์แบบในเครื่องชั่งที่ใหญ่กว่าอย่างมาก:

>>> bigset = set(range(100000))
>>> bigsubset = set(range(50000))
>>> %timeit bigset >= bigsubset
1.14 ms ± 13.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit all(x in bigset for x in bigsubset)
5.96 ms ± 37 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

การใช้การทดสอบส่วนย่อยยังเร็วกว่า แต่เพียงแค่ประมาณ 5 เท่าในระดับนี้ การเพิ่มความเร็วเกิดจากcการใช้งานอย่างรวดเร็วของ Python setแต่อัลกอริทึมพื้นฐานจะเหมือนกันในทั้งสองกรณี

หากของคุณitemsถูกเก็บไว้ในรายการด้วยเหตุผลอื่นคุณจะต้องแปลงเป็นชุดก่อนที่จะใช้วิธีการทดสอบชุดย่อย จากนั้น speedup จะลดลงเหลือประมาณ 2.5x:

>>> %timeit bigset >= set(bigsubseq)
2.1 ms ± 49.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

และถ้าคุณcontainerเป็นลำดับและจำเป็นต้องแปลงก่อนการเร่งความเร็วจะยิ่งเล็กลง:

>>> %timeit set(bigseq) >= set(bigsubseq)
4.36 ms ± 31.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

ครั้งเดียวที่เราได้รับผลลัพธ์ที่ช้าอย่างน่าสยดสยองคือเมื่อเราปล่อยให้containerเป็นลำดับ:

>>> %timeit all(x in bigseq for x in bigsubseq)
184 ms ± 994 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

และแน่นอนเราจะทำอย่างนั้นก็ต่อเมื่อต้องทำ หากรายการทั้งหมดในbigseqนั้นสามารถทำความสะอาดได้เราจะดำเนินการนี้แทน:

>>> %timeit bigset = set(bigseq); all(x in bigset for x in bigsubseq)
7.24 ms ± 78 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

เร็วกว่าทางเลือกเพียง 1.66 เท่า ( set(bigseq) >= set(bigsubseq)กำหนดเวลาไว้ด้านบนที่ 4.36)

ดังนั้นการทดสอบชุดย่อยโดยทั่วไปจึงเร็วกว่า แต่ไม่ได้มาจากอัตรากำไรขั้นต้นที่เหลือเชื่อ ในทางกลับกันมาดูกันว่าเมื่อไหร่allจะเร็วกว่ากัน จะเกิดอะไรขึ้นถ้าitemsค่าสิบล้านมีความยาวและมีแนวโน้มว่าจะมีค่าที่ไม่อยู่ในcontainer?

>>> %timeit hugeiter = (x * 10 for bss in [bigsubseq] * 2000 for x in bss); set(bigset) >= set(hugeiter)
13.1 s ± 167 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
>>> %timeit hugeiter = (x * 10 for bss in [bigsubseq] * 2000 for x in bss); all(x in bigset for x in hugeiter)
2.33 ms ± 65.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

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

นี่เป็นตัวอย่างที่รุนแรงเป็นที่ยอมรับ แต่ตามที่แสดงคุณไม่สามารถสันนิษฐานได้ว่าแนวทางใดวิธีหนึ่งจะเร็วกว่าในทุกกรณี

อัพช็อต

โดยส่วนใหญ่แล้วการแปลงcontainerเป็นชุดจะคุ้มค่าอย่างน้อยถ้าองค์ประกอบทั้งหมดนั้นสามารถทำความสะอาดได้ นั่นเป็นเพราะinสำหรับชุดคือ O (1) ในขณะที่inลำดับคือ O (n)

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


62

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

>>> set(['a','b']).issubset( ['b','a','foo','bar'] )
True

21
เรื่องน่ารู้: set(['a', 'b']) <= set(['b','a','foo','bar'])เป็นอีกวิธีหนึ่งในการสะกดสิ่งเดียวกันและดู "คณิตศาสตร์"
Kirk Strauser

8
ใน Python 2.7 คุณสามารถใช้ได้{'a', 'b'} <= {'b','a','foo','bar'}
Viktor Stískala

11

ฉันค่อนข้างแน่ใจว่าinมีลำดับความสำคัญสูงกว่า,ดังนั้นคำสั่งของคุณจึงถูกตีความว่า'a', ('b' in ['b' ...])ซึ่งจะประเมินว่า'a', Trueตั้งแต่'b'อยู่ในอาร์เรย์

ดูคำตอบก่อนหน้านี้สำหรับวิธีทำสิ่งที่คุณต้องการ


7

หากคุณต้องการที่จะตรวจสอบทั้งหมดของการแข่งขันการป้อนข้อมูลของคุณ ,

>>> all(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])

ถ้าคุณต้องการที่จะตรวจสอบอย่างน้อยการแข่งขันหนึ่ง ,

>>> any(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])

3

ตัวแยกวิเคราะห์ Python ประเมินคำสั่งนั้นเป็นทูเปิลโดยที่ค่าแรกเป็นค่า'a'ที่สองคือนิพจน์'b' in ['b', 'a', 'foo', 'bar'](ซึ่งประเมินเป็นTrue)

คุณสามารถเขียนฟังก์ชันง่ายๆทำในสิ่งที่คุณต้องการแม้ว่า:

def all_in(candidates, sequence):
    for element in candidates:
        if element not in sequence:
            return False
    return True

และเรียกมันว่า:

>>> all_in(('a', 'b'), ['b', 'a', 'foo', 'bar'])
True

2
[x for x in ['a','b'] if x in ['b', 'a', 'foo', 'bar']]

เหตุผลที่ฉันคิดว่านี่ดีกว่าคำตอบที่เลือกคือคุณไม่จำเป็นต้องเรียกใช้ฟังก์ชัน 'all ()' รายการว่างจะประเมินเป็นเท็จในคำสั่ง IF รายการที่ไม่ว่างจะประเมินเป็น True

if [x for x in ['a','b'] if x in ['b', 'a', 'foo', 'bar']]:
    ...Do something...

ตัวอย่าง:

>>> [x for x in ['a','b'] if x in ['b', 'a', 'foo', 'bar']]
['a', 'b']
>>> [x for x in ['G','F'] if x in ['b', 'a', 'foo', 'bar']]
[]

1

ฉันจะบอกว่าเราสามารถเว้นวงเล็บเหลี่ยมเหล่านั้นไว้ได้

array = ['b', 'a', 'foo', 'bar']
all([i in array for i in 'a', 'b'])

0

คำตอบทั้งสองที่นำเสนอที่นี่จะไม่จัดการกับองค์ประกอบที่ซ้ำกัน ตัวอย่างเช่นหากคุณกำลังทดสอบว่า [1,2,2] เป็นรายการย่อยของ [1,2,3,4] หรือไม่ทั้งสองรายการจะคืนค่า True นั่นอาจเป็นสิ่งที่คุณตั้งใจจะทำ แต่ฉันแค่อยากจะชี้แจง หากคุณต้องการส่งคืนค่าเท็จสำหรับ [1,2,2] ใน [1,2,3,4] คุณจะต้องเรียงลำดับทั้งสองรายการและตรวจสอบแต่ละรายการด้วยดัชนีเคลื่อนที่ในแต่ละรายการ มีความซับซ้อนกว่าเล็กน้อยสำหรับการวนซ้ำ


1
'ทั้ง'? มีมากกว่าสองคำตอบ คุณหมายความว่าคำตอบทั้งหมดประสบปัญหานี้หรือมีเพียงสองคำตอบ (และถ้าเป็นเช่นนั้นข้อใด)
Wipqozn

-1

คุณจะเป็น pythonic โดยไม่มี lambdas ได้อย่างไร! .. ไม่ต้องจริงจัง .. แต่วิธีนี้ก็ได้ผลเช่นกัน:

orig_array = [ ..... ]
test_array = [ ... ]

filter(lambda x:x in test_array, orig_array) == test_array

เว้นส่วนท้ายไว้หากคุณต้องการทดสอบว่ามีค่าใดอยู่ในอาร์เรย์หรือไม่:

filter(lambda x:x in test_array, orig_array)

1
โปรดทราบว่าสิ่งนี้จะไม่ทำงานตามที่ตั้งใจไว้ใน Python 3 ซึ่งfilterเป็นเครื่องกำเนิดไฟฟ้า คุณจะต้องรวมไว้ในlistกรณีที่คุณต้องการให้ได้ผลลัพธ์ที่คุณสามารถทดสอบด้วย==หรือในบริบทบูลีน (เพื่อดูว่าว่างเปล่า) การใช้ความเข้าใจรายการหรือนิพจน์ตัวสร้างในanyหรือallดีกว่า
Blckknght

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