Bisakah Python menguji keanggotaan beberapa nilai dalam daftar?

122

Saya ingin menguji apakah dua atau lebih nilai memiliki keanggotaan dalam daftar, tetapi saya mendapatkan hasil yang tidak terduga:

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

Jadi, Bisakah Python menguji keanggotaan beberapa nilai sekaligus dalam daftar? Apa arti hasil itu?

Noe Nieto
sumber

Jawaban:

198

Ini melakukan apa yang Anda inginkan, dan akan berhasil di hampir semua kasus:

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

Ekspresi 'a','b' in ['b', 'a', 'foo', 'bar']tidak berfungsi seperti yang diharapkan karena Python menafsirkannya sebagai tupel:

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

Pilihan lain

Ada cara lain untuk menjalankan pengujian ini, tetapi cara tersebut tidak akan berfungsi untuk berbagai jenis masukan. Seperti yang dikatakan Kabie , Anda dapat menyelesaikan masalah ini menggunakan set ...

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

...terkadang:

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

Set hanya dapat dibuat dengan elemen yang dapat dicirikan. Tetapi ekspresi generator all(x in container for x in items)dapat menangani hampir semua jenis penampung. Satu-satunya persyaratan adalah yang containerdapat diulang (yaitu bukan generator). itemsbisa menjadi iterable sama sekali.

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

Tes Kecepatan

Dalam banyak kasus, pengujian subset akan lebih cepat daripada all, tetapi perbedaannya tidak mengejutkan - kecuali jika pertanyaannya tidak relevan karena rangkaian bukanlah pilihan. Mengubah daftar menjadi set hanya untuk tujuan pengujian seperti ini tidak selalu sepadan dengan masalahnya. Dan mengubah generator menjadi set terkadang bisa sangat boros, memperlambat program dengan banyak kali lipat.

Berikut adalah beberapa tolok ukur untuk ilustrasi. Perbedaan terbesar muncul jika keduanya containerdan itemsrelatif kecil. Dalam hal ini, pendekatan subset adalah tentang urutan besarnya lebih cepat:

>>> 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)

Ini terlihat seperti perbedaan besar. Tapi selama containersatu set, allmasih bisa digunakan dengan sempurna pada skala yang jauh lebih besar:

>>> 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)

Menggunakan pengujian subset masih lebih cepat, tetapi hanya sekitar 5x pada skala ini. Peningkatan kecepatan ini disebabkan oleh cimplementasi fast- backed Python set, tetapi algoritme dasarnya sama dalam kedua kasus.

Jika Anda itemssudah disimpan dalam daftar karena alasan lain, maka Anda harus mengubahnya menjadi satu set sebelum menggunakan pendekatan pengujian subset. Kemudian speedup turun menjadi sekitar 2,5x:

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

Dan jika Anda containeradalah urutan, dan perlu dikonversi terlebih dahulu, maka percepatannya bahkan lebih kecil:

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

Satu-satunya saat kita mendapatkan hasil yang sangat lambat adalah ketika kita pergi containersecara berurutan:

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

Dan tentu saja, kami hanya akan melakukannya jika kami harus. Jika semua item di bigseqhashable, maka kami akan melakukan ini sebagai gantinya:

>>> %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)

Itu hanya 1,66x lebih cepat dari alternatif ( set(bigseq) >= set(bigsubseq), waktunya di atas pada 4,36).

Jadi pengujian subset umumnya lebih cepat, tetapi tidak dengan margin yang luar biasa. Di sisi lain, mari kita lihat kapan alllebih cepat. Bagaimana jika itemssepuluh juta nilai panjangnya, dan cenderung memiliki nilai yang tidak ada 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)

Mengubah generator menjadi satu set ternyata sangat boros dalam hal ini. The setkonstruktor harus mengkonsumsi seluruh pembangkit. Tetapi perilaku hubung singkat allmemastikan bahwa hanya sebagian kecil dari generator yang perlu dikonsumsi, jadi ini lebih cepat daripada pengujian subset dengan empat kali lipat .

Ini adalah contoh ekstrim, harus diakui. Tetapi seperti yang ditunjukkan, Anda tidak dapat berasumsi bahwa satu pendekatan atau yang lain akan lebih cepat dalam semua kasus.

Hasilnya

Sebagian besar waktu, mengonversi containerke satu set sangat berharga, setidaknya jika semua elemennya dapat di-hash. Itu karena inuntuk himpunan adalah O (1), sedangkan inuntuk sekuens adalah O (n).

Di sisi lain, menggunakan pengujian subset mungkin hanya sesekali bermanfaat. Pasti lakukan jika item pengujian Anda sudah disimpan dalam satu set. Jika tidak, allhanya sedikit lebih lambat, dan tidak memerlukan penyimpanan tambahan. Ini juga dapat digunakan dengan generator item yang besar, dan terkadang memberikan percepatan besar dalam kasus itu.

pengirim
sumber
62

Cara lain untuk melakukannya:

>>> set(['a','b']).issubset( ['b','a','foo','bar'] )
True
Kabie
sumber
21
Fakta menyenangkan: set(['a', 'b']) <= set(['b','a','foo','bar'])adalah cara lain untuk mengeja hal yang sama, dan terlihat "lebih matematis".
Kirk Strauser
8
Pada Python 2.7 Anda dapat menggunakan{'a', 'b'} <= {'b','a','foo','bar'}
Viktor Stískala
11

Saya cukup yakin inmemiliki prioritas yang lebih tinggi daripada ,pernyataan Anda diinterpretasikan sebagai 'a', ('b' in ['b' ...]), yang kemudian dievaluasi 'a', Truekarena 'b'ada dalam array.

Lihat jawaban sebelumnya untuk mengetahui cara melakukan apa yang Anda inginkan.

Foon
sumber
7

Jika Anda ingin memeriksa semua kecocokan masukan Anda ,

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

jika Anda ingin memeriksa setidaknya satu pertandingan ,

>>> any(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])
Mohideen bin Mohammed
sumber
3

Pengurai Python mengevaluasi pernyataan itu sebagai tupel, di mana nilai pertama adalah 'a', dan nilai kedua adalah ekspresi 'b' in ['b', 'a', 'foo', 'bar'](yang mengevaluasi ke True).

Anda dapat menulis fungsi sederhana melakukan apa yang Anda inginkan, meskipun:

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

Dan menyebutnya seperti:

>>> all_in(('a', 'b'), ['b', 'a', 'foo', 'bar'])
True
dcrosta.dll
sumber
2
[x for x in ['a','b'] if x in ['b', 'a', 'foo', 'bar']]

Alasan saya pikir ini lebih baik daripada jawaban yang dipilih adalah karena Anda benar-benar tidak perlu memanggil fungsi 'all ()'. Daftar kosong mengevaluasi ke False dalam pernyataan IF, daftar tidak kosong mengevaluasi ke True.

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

Contoh:

>>> [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']]
[]
dmchdev
sumber
1

Saya akan mengatakan kita bahkan bisa membiarkan tanda kurung siku itu keluar.

array = ['b', 'a', 'foo', 'bar']
all([i in array for i in 'a', 'b'])
szabadkai.dll
sumber
0

Kedua jawaban yang disajikan di sini tidak akan menangani elemen yang berulang. Misalnya, jika Anda menguji apakah [1,2,2] adalah sublist dari [1,2,3,4], keduanya akan mengembalikan True. Mungkin itu yang ingin Anda lakukan, tetapi saya hanya ingin menjelaskan. Jika Anda ingin mengembalikan false untuk [1,2,2] di [1,2,3,4], Anda perlu mengurutkan kedua daftar dan memeriksa setiap item dengan indeks bergerak pada setiap daftar. Sedikit lebih rumit untuk perulangan.

pengguna1419042
sumber
1
'kedua'? Ada lebih dari dua jawaban. Apakah maksud Anda semua jawaban menderita dari masalah ini, atau hanya dua jawaban (dan jika demikian, yang mana)?
Wipqozn
-1

bagaimana Anda bisa menjadi pythonic tanpa lambda! .. tidak untuk dianggap serius .. tapi cara ini juga berhasil:

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

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

tinggalkan bagian akhir jika Anda ingin menguji apakah ada nilai di dalam array:

filter(lambda x:x in test_array, orig_array)
Kandang kelinci
sumber
1
Perlu diketahui bahwa ini tidak akan berfungsi sebagaimana dimaksud dalam Python 3 di mana filtergenerator. Anda perlu membungkusnya listjika Anda ingin benar-benar mendapatkan hasil yang bisa Anda uji dengan ==atau dalam konteks boolean (untuk melihat apakah kosong). Menggunakan pemahaman daftar atau ekspresi generator dalam anyatau alllebih disukai.
Blckknght
-1

Begini cara saya melakukannya:

A = ['a','b','c']
B = ['c']
logic = [(x in B) for x in A]
if True in logic:
    do something
John
sumber