Memfilter daftar berdasarkan daftar boolean

127

Saya memiliki daftar nilai yang perlu saya filter mengingat nilai dalam daftar boolean:

list_a = [1, 2, 4, 6]
filter = [True, False, True, False]

Saya membuat daftar yang difilter baru dengan baris berikut:

filtered_list = [i for indx,i in enumerate(list_a) if filter[indx] == True]

yang mengakibatkan:

print filtered_list
[1,4]

Garis bekerja tetapi terlihat (bagi saya) sedikit berlebihan dan saya bertanya-tanya apakah ada cara yang lebih sederhana untuk mencapai hal yang sama.


Saran

Ringkasan dua nasihat baik yang diberikan dalam jawaban di bawah ini:

1- Jangan beri nama daftar filterseperti yang saya lakukan karena ini adalah fungsi bawaan.

2- Jangan membandingkan hal-hal Trueseperti yang saya lakukan dengan if filter[idx]==True..karena itu tidak perlu. Cukup menggunakan if filter[idx]sudah cukup.

Gabriel
sumber
3
Hanya FYI, ini adalah komputasi paralel primitif umum yang disebut pemadatan aliran . (Ini disebut 'primitif' bukan karena sederhana, tetapi karena digunakan sebagai blok pembangun untuk banyak algoritma paralel lainnya)
BlueRaja - Danny Pflughoeft
2
Beberapa gaya catatan: if filter[indx] == TrueDo tidak menggunakan== jika Anda ingin memeriksa identitas dengan True, penggunaan is. Pokoknya dalam hal ini seluruh perbandingan tidak berguna, Anda bisa menggunakannya if filter[indx]. Terakhir: jangan pernah menggunakan nama built-in sebagai nama variabel / modul (saya mengacu pada nama filter). Menggunakan sesuatu seperti included, sehingga ifterbaca dengan baik ( if included[indx]).
Bakuriu

Jawaban:

184

Anda sedang mencari itertools.compress:

>>> from itertools import compress
>>> list_a = [1, 2, 4, 6]
>>> fil = [True, False, True, False]
>>> list(compress(list_a, fil))
[1, 4]

Perbandingan waktu (py3.x):

>>> list_a = [1, 2, 4, 6]
>>> fil = [True, False, True, False]
>>> %timeit list(compress(list_a, fil))
100000 loops, best of 3: 2.58 us per loop
>>> %timeit [i for (i, v) in zip(list_a, fil) if v]  #winner
100000 loops, best of 3: 1.98 us per loop

>>> list_a = [1, 2, 4, 6]*100
>>> fil = [True, False, True, False]*100
>>> %timeit list(compress(list_a, fil))              #winner
10000 loops, best of 3: 24.3 us per loop
>>> %timeit [i for (i, v) in zip(list_a, fil) if v]
10000 loops, best of 3: 82 us per loop

>>> list_a = [1, 2, 4, 6]*10000
>>> fil = [True, False, True, False]*10000
>>> %timeit list(compress(list_a, fil))              #winner
1000 loops, best of 3: 1.66 ms per loop
>>> %timeit [i for (i, v) in zip(list_a, fil) if v] 
100 loops, best of 3: 7.65 ms per loop

Jangan gunakan filtersebagai nama variabel, itu adalah fungsi bawaan.

Ashwini Chaudhary
sumber
@Mehdi Saya menemukan cara Matlab sangat tidak intuitif, tapi saya kira itu tergantung pada apa yang Anda terbiasa.
Ian Goldby
Bagaimana saya bisa memilih [2, 6]?
Florent
Saya mengerti, list(compress(list_a, [not i for i in fill]))harus kembali[2, 6]
Florent
42

Seperti itu:

filtered_list = [i for (i, v) in zip(list_a, filter) if v]

Menggunakan zipadalah cara pythonic untuk beralih pada beberapa urutan secara paralel, tanpa perlu pengindeksan apa pun. Ini mengasumsikan kedua sekuens memiliki panjang yang sama (zip berhenti setelah kehabisan terpendek). Menggunakan itertoolsuntuk kasus sederhana semacam itu agak berlebihan ...

Satu hal yang Anda lakukan dalam contoh Anda, yang benar-benar harus Anda hentikan adalah membandingkan berbagai hal dengan True, ini biasanya tidak perlu. Alih-alih if filter[idx]==True: ..., Anda cukup menulis if filter[idx]: ....

Bas Swinckels
sumber
40

Dengan numpy:

In [128]: list_a = np.array([1, 2, 4, 6])
In [129]: filter = np.array([True, False, True, False])
In [130]: list_a[filter]

Out[130]: array([1, 4])

atau lihat jawaban Alex Szatmary jika list_a dapat berupa array numpy tetapi tidak filter

Numpy biasanya memberi Anda dorongan kecepatan besar juga

In [133]: list_a = [1, 2, 4, 6]*10000
In [134]: fil = [True, False, True, False]*10000
In [135]: list_a_np = np.array(list_a)
In [136]: fil_np = np.array(fil)

In [139]: %timeit list(itertools.compress(list_a, fil))
1000 loops, best of 3: 625 us per loop

In [140]: %timeit list_a_np[fil_np]
10000 loops, best of 3: 173 us per loop
Palu
sumber
Poin yang bagus, saya lebih suka menggunakan NumPylebih dari yang listmungkin. Tetapi jika Anda harus listtetap menggunakan , Anda memiliki (menggunakan NumPysolusi) buat np.arraydari kedua daftar, gunakan pengindeksan boolean dan akhirnya mengubah array kembali ke daftar dengan tolist()metode. Untuk lebih tepatnya, Anda harus memasukkan benda-benda itu ke dalam perbandingan waktu. Kemudian, menggunakan itertools.compressakan tetap menjadi solusi tercepat.
Nerxis
17

Untuk melakukan ini menggunakan numpy, yaitu, jika Anda memiliki array a,, bukannya list_a:

a = np.array([1, 2, 4, 6])
my_filter = np.array([True, False, True, False], dtype=bool)
a[my_filter]
> array([1, 4])
Alex Szatmary
sumber
3
Jika Anda mengubah my_filter menjadi array boolean, Anda dapat menggunakan pengindeksan boolean langsung, tanpa perlu where.
Bas Swinckels
1
filtered_list = [list_a[i] for i in range(len(list_a)) if filter[i]]
Daniel Braun
sumber
-1

Dengan python 3 Anda bisa menggunakan list_a[filter]untuk mendapatkan Truenilai. Untuk mendapatkan Falsenilai, gunakanlist_a[~filter]

Franklin'j Gil'z
sumber