Filtraggio di un elenco in base a un elenco di valori booleani


127

Ho un elenco di valori che devo filtrare dati i valori in un elenco di valori booleani:

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

Genero un nuovo elenco filtrato con la seguente riga:

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

che si traduce in:

print filtered_list
[1,4]

La linea funziona ma sembra (per me) un po 'eccessiva e mi chiedevo se ci fosse un modo più semplice per ottenere lo stesso.


consigli

Riepilogo di due buoni consigli forniti nelle risposte seguenti:

1- Non nominare un elenco filtercome ho fatto perché è una funzione integrata.

2- Non confrontare le cose con Truequelle che ho fatto if filter[idx]==True..visto che non sono necessarie. Basta usare if filter[idx]è abbastanza.


3
Cordiali saluti, questa è una comune primitiva di calcolo parallelo chiamata stream compaction . (Si chiama "primitivo" non perché sia ​​semplice, ma perché è usato come componente per molti altri algoritmi paralleli)
BlueRaja - Danny Pflughoeft

2
Alcune note di stile: if filter[indx] == TrueDo Non utilizzare ==se si vuole verificare la presenza di identità True, utilizzo is. Comunque in questo caso l'intero confronto è inutile, potresti semplicemente usarlo if filter[indx]. Infine: non usare mai il nome di un built-in come nome di variabile / modulo (mi riferisco al nome filter). Usando qualcosa del genere included, in modo che la iflettura sia corretta ( if included[indx]).
Bakuriu,

Risposte:


184

Stai cercando itertools.compress:

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

Confronti di temporizzazione (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

Non utilizzare filtercome nome di variabile, è una funzione integrata.


@Mehdi Trovo il modo Matlab estremamente poco intuitivo, ma suppongo che dipenda da ciò a cui sei abituato.
Ian Goldby,

Come posso selezionare [2, 6]?
Florent,

Capisco, list(compress(list_a, [not i for i in fill]))dovrebbe tornare[2, 6]
Florent

42

Così:

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

L'utilizzo zipè il modo pitonico di iterare su più sequenze in parallelo, senza bisogno di indicizzazione. Ciò presuppone che entrambe le sequenze abbiano la stessa lunghezza (lo zip si arresta dopo il termine più breve). Usare itertoolsper un caso così semplice è un po 'eccessivo ...

Una cosa che fai nel tuo esempio che dovresti davvero smettere di fare è confrontare le cose con True, questo di solito non è necessario. Invece di if filter[idx]==True: ..., puoi semplicemente scrivere if filter[idx]: ....


40

Con intorpidimento:

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

oppure vedi la risposta di Alex Szatmary se list_a può essere un array intorpidito ma non filtrare

Numpy di solito ti dà anche un grande aumento di velocità

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

Buon punto, preferisco usare NumPyoltre listdove possibile. Ma se è necessario utilizzare listcomunque, è necessario (utilizzando la NumPysoluzione) creare np.arrayda entrambi gli elenchi, utilizzare l'indicizzazione booleana e infine riconvertire l'array in elenco con il tolist()metodo. Per essere precisi, dovresti includere la creazione di quegli oggetti nel confronto temporale. Quindi, l'utilizzo itertools.compresssarà ancora la soluzione più veloce.
Nerxis,

17

Per fare ciò, usa numpy, cioè se hai un array a, invece di 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])

3
Se trasformi my_filter in un array booleano, puoi utilizzare l'indicizzazione booleana diretta, senza necessità where.
Bas Swinckels,

1
filtered_list = [list_a[i] for i in range(len(list_a)) if filter[i]]

-1

Con Python 3 puoi usare list_a[filter]per ottenere Truevalori. Per ottenere Falsevalori utilizzarelist_a[~filter]

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.