Пошук індексу елементів на основі умови за допомогою розуміння списку 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.

Знайшовши наступне на нумерному веб-сайті, я думаю, що знайшов рішення, яке мені подобається:

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

Відповіді:


77
  • У Python ви взагалі не використовуєте для цього індекси, а просто маєте справу зі значеннями [value for value in a if value > 2]. Зазвичай робота з індексами означає, що ви не робите щось найкраще.

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

    >>> 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
у вас є списки, один для діапазонів і один для кутів, ви хочете відфільтрувати значення діапазону, що перевищують деякий поріг. Як ви також фільтруєте кути, відповідні цим діапазонам, "найкращим чином"?
Мехді

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. Пітонічніше було б використовувати генератори та ітератори.


2
ОП міг би записати це як [i for i,v in enumerate(a) if v > 2]натомість.
NullUserException

Це не коротше, а довше. Замініть indexна iі valueз vоригіналом і порахуйте символи.
agf

@NullUser, agf: ти маєш рацію, але головний момент - друга частина :)
Елі Бендерський

1
Використання enumerateпонад range(len(...))є більш надійним та ефективним.
Майк Грехем

1
@Mike Graham: Я згоден - змінить find_indicesфункцію на використанняenumerate
Елі Бендерський,


6

Можливо, ще одне питання: "що ти будеш робити з цими показниками, як тільки їх отримаєш?" Якщо ви збираєтеся використовувати їх для створення іншого списку, то в Python вони є непотрібним середнім кроком. Якщо ви хочете, щоб усі значення відповідали заданій умові, просто використовуйте вбудований фільтр:

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

Або напишіть власний список відповідей:

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

Якщо ви хочете видалити їх зі списку, то пітонічний спосіб - це не обов’язково видаляти зі списку, а писати розуміння списку так, ніби ви створюєте новий список, і присвоюєте його на місці за допомогою listvar[:]ліворуч -бік:

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

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


Павло, я ще не стикався з необхідністю цього в сценарії / функції / класі. Це більше для інтерактивного тестування класу, про який я пишу.
Лі

@Mike - дякую за редагування, але я дійсно мав на увазі a[:] = ...- дивіться відповідь Алекса Мартеллі на це питання stackoverflow.com/questions/1352885/… .
PaulMcG

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

На мою уяву ідеальним способом було б складання дочірнього класу списку та додавання індексів як метод класу. Таким чином знадобиться лише метод фільтра:

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.