Фильтрация списка на основе списка логических значений

127

У меня есть список значений, которые мне нужно отфильтровать по значениям в списке логических значений:

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

Я создаю новый отфильтрованный список со следующей строкой:

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

что приводит к:

print filtered_list
[1,4]

Линия работает, но выглядит (для меня) немного излишне, и мне было интересно, есть ли более простой способ добиться того же.


Советы

Резюме двух хороших советов, приведенных в ответах ниже:

1- Не называйте список filter , как я сделал , потому что это встроенная функция.

2- Не сравнивайте вещи с тем, Trueчто делал я, if filter[idx]==True..потому что в этом нет необходимости. Достаточно просто использовать if filter[idx].

Габриель
источник
3
К вашему сведению, это обычный примитив параллельных вычислений, называемый сжатием потока . (Его называют «примитивом» не потому, что он прост, а потому, что он используется в качестве строительного блока для многих других параллельных алгоритмов)
BlueRaja - Дэнни Пфлугофт
2
Некоторые примечания к стилю: if filter[indx] == TrueDo ли не использовать , ==если вы хотите , чтобы проверить идентичность с True, использованием is. В любом случае в этом случае все сравнение бесполезно, вы можете просто использовать if filter[indx]. И наконец: никогда не используйте имя встроенного в качестве имени переменной / модуля (я имею в виду имя filter). Используя что-то вроде included, чтобы ifхорошо читалось ( if included[indx]).
Bakuriu

Ответы:

184

Вы ищете itertools.compress:

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

Сравнение времени (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

Не используйте filterв качестве имени переменной, это встроенная функция.

Ашвини Чаудхари
источник
@Mehdi Я нахожу способ Matlab очень неинтуитивным, но полагаю, это зависит от того, к чему вы привыкли.
Ян Голдби
Как я могу выбрать [2, 6]?
Флоран
Я понял, list(compress(list_a, [not i for i in fill]))должен вернуться[2, 6]
Флоран
42

Вот так:

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

Используя zipэто вещий способ параллельного перебора нескольких последовательностей без необходимости индексации. Это предполагает, что обе последовательности имеют одинаковую длину (почтовый индекс останавливается после того, как истекает самый короткий). Использование itertoolsдля такого простого случая немного излишне ...

Одна вещь, которую вы делаете в своем примере, вам действительно стоит прекратить, - это сравнивать вещи с True, обычно в этом нет необходимости. Вместо этого if filter[idx]==True: ...вы можете просто написать if filter[idx]: ....

Бас Суинкелс
источник
40

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

или см. ответ Алекса Сатмари, если list_a может быть массивом numpy, но не фильтром

Numpy обычно дает вам большой прирост скорости

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
молоток
источник
Хороший вопрос, я предпочитаю использовать NumPyболее , listгде это возможно. Но если вам listвсе равно нужно использовать , вы должны (используя NumPyрешение) создать np.arrayиз обоих списков, использовать логическую индексацию и, наконец, преобразовать массив обратно в список с помощью tolist()метода. Чтобы быть точным, вы должны включить создание этих объектов в сравнение по времени. Тогда использование itertools.compressбудет по-прежнему самым быстрым решением.
Nerxis
17

Для этого используйте numpy, то есть если у вас есть массив a, вместо 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
Если вы превратите my_filter в логический массив, вы можете использовать прямую логическую индексацию без необходимости where.
Bas Swinckels
1
filtered_list = [list_a[i] for i in range(len(list_a)) if filter[i]]
Даниэль Браун
источник
-1

С помощью python 3 вы можете использовать list_a[filter]для получения Trueзначений. Чтобы получить Falseзначения, используйтеlist_a[~filter]

Франклин'дж Гил'з
источник