Filtrer une liste en fonction d'une liste de booléens

127

J'ai une liste de valeurs que je dois filtrer étant donné les valeurs dans une liste de booléens:

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

Je génère une nouvelle liste filtrée avec la ligne suivante:

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

ce qui se traduit par:

print filtered_list
[1,4]

La ligne fonctionne mais me semble un peu exagérée et je me demandais s'il y avait un moyen plus simple d'y parvenir.


Des conseils

Résumé de deux bons conseils donnés dans les réponses ci-dessous:

1- Ne nommez pas une liste filtercomme je l'ai fait car c'est une fonction intégrée.

2- Ne comparez pas les choses à ce Trueque j'ai fait if filter[idx]==True..car c'est inutile. Il suffit d'utiliser if filter[idx].

Gabriel
la source
3
Juste pour info, il s'agit d'une primitive de calcul parallèle commune appelée compactage de flux . (On l'appelle une `` primitive '' non pas parce qu'elle est simple, mais parce qu'elle est utilisée comme bloc de construction pour de nombreux autres algorithmes parallèles)
BlueRaja - Danny Pflughoeft
2
Quelques notes de style: if filter[indx] == Truene pas utiliser ==si vous souhaitez vérifier l'identité avec True, utilisez is. Quoi qu'il en soit, dans ce cas, toute la comparaison est inutile, vous pouvez simplement utiliser if filter[indx]. Enfin: n'utilisez jamais le nom d'un élément intégré comme nom de variable / module (je fais référence au nom filter). En utilisant quelque chose comme included, pour que le iflit bien ( if included[indx]).
Bakuriu

Réponses:

184

Vous recherchez itertools.compress:

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

Comparaisons de temps (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

Ne pas utiliser filtercomme nom de variable, c'est une fonction intégrée.

Ashwini Chaudhary
la source
@Mehdi Je trouve la méthode Matlab très peu intuitive, mais je suppose que cela dépend de ce à quoi vous êtes habitué.
Ian Goldpar
Comment puis-je sélectionner [2, 6]?
Florent
Je comprends, list(compress(list_a, [not i for i in fill]))devrait revenir[2, 6]
Florent
42

Ainsi:

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

L'utilisation zipest la manière pythonique d'itérer sur plusieurs séquences en parallèle, sans avoir besoin d'indexation. Cela suppose que les deux séquences ont la même longueur (le zip s'arrête après la fin du plus court). Utiliser itertoolspour un cas aussi simple est un peu exagéré ...

Une chose que vous faites dans votre exemple que vous devriez vraiment arrêter de faire est de comparer les choses à True, ce n'est généralement pas nécessaire. Au lieu de if filter[idx]==True: ..., vous pouvez simplement écrire if filter[idx]: ....

Bas Swinckels
la source
40

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

ou voir la réponse d'Alex Szatmary si list_a peut être un tableau numpy mais pas un filtre

Numpy vous donne généralement une grande augmentation de vitesse

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
Marteau
la source
Bon point, je préfère utiliser NumPyplus listsi possible. Mais si vous devez listquand même utiliser , vous devez (en utilisant la NumPysolution) créer à np.arraypartir des deux listes, utiliser l'indexation booléenne et enfin convertir le tableau en liste avec la tolist()méthode. Pour être précis, vous devez inclure la création de ces objets dans la comparaison temporelle. Ensuite, l'utilisation itertools.compresssera toujours la solution la plus rapide.
Nerxis
17

Pour ce faire, utilisez numpy, c'est-à-dire si vous avez un tableau a, au lieu de 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
la source
3
Si vous transformez my_filter en un tableau booléen, vous pouvez utiliser l'indexation booléenne directe, sans avoir besoin de where.
Bas Swinckels
1
filtered_list = [list_a[i] for i in range(len(list_a)) if filter[i]]
Daniel Braun
la source
-1

Avec python 3, vous pouvez utiliser list_a[filter]pour obtenir des Truevaleurs. Pour obtenir des Falsevaleurs, utilisezlist_a[~filter]

Franklin'j Gil'z
la source