Поиск индекса элементов на основе условия с использованием понимания списка Python

119

Следующий код Python выглядит очень длинным при использовании фона Matlab.

>>> a = [1, 2, 3, 1, 2, 3]
>>> [index for index,value in enumerate(a) if value > 2]
[2, 5]

В Matlab я могу написать:

>> a = [1, 2, 3, 1, 2, 3];
>> find(a>2)
ans =
     3     6

Есть ли короткий способ написать это на Python, или я просто буду придерживаться длинной версии?


Спасибо за все предложения и объяснения причин синтаксиса Python.

Найдя на сайте numpy следующее, я думаю, что нашел решение, которое мне нравится:

http://docs.scipy.org/doc/numpy/user/basics.indexing.html#boolean-or-mask-index-arrays

Применение информации с этого веб-сайта к моей проблеме выше дало бы следующее:

>>> from numpy import array
>>> a = array([1, 2, 3, 1, 2, 3])
>>> b = a>2 
array([False, False, True, False, False, True], dtype=bool)
>>> r = array(range(len(b)))
>>> r(b)
[2, 5]

Следующее должно работать (но у меня нет под рукой интерпретатора Python для его тестирования):

class my_array(numpy.array):
    def find(self, b):
        r = array(range(len(b)))
        return r(b)


>>> a = my_array([1, 2, 3, 1, 2, 3])
>>> a.find(a>2)
[2, 5]
подветренный
источник
6
Как насчет [idx for idx in range(len(a)) if a[idx] > 2]? Причина, по которой это немного неудобно делать в Python, заключается в том, что он не использует индексы так часто, как другие языки.
NullUserException 01

Ответы:

77
  • В Python вы вообще не будете использовать для этого индексы, а просто будете иметь дело со значениями - [value for value in a if value > 2]. Обычно работа с индексами означает, что вы делаете что-то не лучшим образом.

  • Если вам действительно нужен API , похожий на Matlab, вы бы использовать NumPy , пакет для многомерных массивов и численной математики в Python , который сильно вдохновлен Matlab. Вы бы использовали массив numpy вместо списка.

    >>> import numpy
    >>> a = numpy.array([1, 2, 3, 1, 2, 3])
    >>> a
    array([1, 2, 3, 1, 2, 3])
    >>> numpy.where(a > 2)
    (array([2, 5]),)
    >>> a > 2
    array([False, False,  True, False, False,  True], dtype=bool)
    >>> a[numpy.where(a > 2)]
    array([3, 3])
    >>> a[a > 2]
    array([3, 3])
Майк Грэм
источник
2
у вас есть списки, один для диапазонов, а другой для углов, вы хотите отфильтровать значения диапазона, превышающие некоторый порог. Как вы также «наилучшим образом» фильтруете углы, соответствующие этим диапазонам?
Mehdi
3
filtered_ranges_and_angles = [(range, angle) for range, angle in zip(ranges, angles) if should_be_kept(range)]
Майк Грэм
7
«В Python вы вообще не будете использовать для этого индексы, а просто имеете дело со значениями», - это утверждение показывает, что вы недостаточно провели анализ данных и моделирование машинного обучения. Индексы одного тензора на основе определенного условия используются для фильтрации другого тензора.
horaceT
63

По-другому:

>>> [i for i in range(len(a)) if a[i] > 2]
[2, 5]

В общем, помните, что, хотя findэто уже готовая функция, составление списков - это общее и, следовательно, очень мощное решение . Ничто не мешает написать findфункцию на Python и использовать ее в дальнейшем по своему желанию. То есть:

>>> def find_indices(lst, condition):
...   return [i for i, elem in enumerate(lst) if condition(elem)]
... 
>>> find_indices(a, lambda e: e > 2)
[2, 5]

Обратите внимание, что здесь я использую списки для имитации Matlab. Было бы более Pythonic использовать генераторы и итераторы.

Эли Бендерский
источник
2
OP мог бы написать это как [i for i,v in enumerate(a) if v > 2].
NullUserException 01
Это не короче, а длиннее. Заменить indexс iи valueс vв оригинале и сосчитать символы.
agf 01
@NullUser, agf: вы правы, но главное - это вторая часть :)
Эли Бендерский
1
Использование enumerateover range(len(...))более надежно и эффективно.
Майк Грэм
1
@Mike Graham: Я согласен - изменю find_indicesфункцию для использованияenumerate
Эли Бендерски
22

Для меня это хорошо работает:

>>> import numpy as np
>>> a = np.array([1, 2, 3, 1, 2, 3])
>>> np.where(a > 2)[0]
[2 5]
Александр Киберман
источник
6

Может быть, другой вопрос: «Что вы собираетесь делать с этими индексами, когда они у вас есть?» Если вы собираетесь использовать их для создания другого списка, то в Python они являются ненужным промежуточным шагом. Если вам нужны все значения, которые соответствуют заданному условию, просто используйте встроенный фильтр:

matchingVals = filter(lambda x : x>2, a)

Или напишите свой собственный список:

matchingVals = [x for x in a if x > 2]

Если вы хотите удалить их из списка, то способ Pythonic не обязательно удалять из списка, а написать понимание списка, как если бы вы создавали новый список, и назначаете его обратно на месте с listvar[:]помощью слева -сторона:

a[:] = [x for x in a if x <= 2]

Matlab поставляет, findпотому что его модель, ориентированная на массивы, работает путем выбора элементов с использованием их индексов массива. Вы , конечно, можете сделать это в Python, но более питонический способ заключается в использовании итераторов и генераторов, как уже упоминалось @EliBendersky.

PaulMcG
источник
Пол, я еще не сталкивался с необходимостью в этом в скрипте / функции / классе. Это больше для интерактивного тестирования класса, который я пишу.
Ли,
@Mike - спасибо за редактирование, но я действительно имел в виду a[:] = ...- см. Ответ Алекса Мартелли на этот вопрос stackoverflow.com/questions/1352885/… .
PaulMcG 02
@Paul, я предположил (и надеялся!), Что вы на самом деле не имели в виду это из вашего описания, что вы собирались «создать новый список»; Я считаю, что программы, как правило, легче понять и поддерживать, когда они очень экономно изменяют существующие данные. В любом случае, извините, что перешагнул - вы, безусловно, сможете отредактировать свой пост так, как хотите.
Майк Грэм
6

Даже если это запоздалый ответ: я думаю, что это все еще очень хороший вопрос, и IMHO Python (без дополнительных библиотек или инструментов, таких как numpy) по-прежнему не имеет удобного метода для доступа к индексам элементов списка в соответствии с вручную определенным фильтром.

Вы можете вручную определить функцию, которая обеспечивает эту функциональность:

def indices(list, filtr=lambda x: bool(x)):
    return [i for i,x in enumerate(list) if filtr(x)]

print(indices([1,0,3,5,1], lambda x: x==1))

Доходность: [0, 4]

В моем воображении идеальным способом было бы создать дочерний класс списка и добавить функцию index как метод класса. Таким образом, потребуется только метод фильтрации:

class MyList(list):
    def __init__(self, *args):
        list.__init__(self, *args)
    def indices(self, filtr=lambda x: bool(x)):
        return [i for i,x in enumerate(self) if filtr(x)]

my_list = MyList([1,0,3,5,1])
my_list.indices(lambda x: x==1)

Я подробно остановился на этой теме здесь: http://tinyurl.com/jajrr87

Герхард Хагерер
источник