Python: возвращает индекс первого элемента списка, который делает переданную функцию истинной - PullRequest
45 голосов
/ 09 ноября 2009

Функция list.index(x) возвращает индекс в списке первого элемента со значением x.

Существует ли функция list_func_index(), аналогичная функции index(), которая имеет функцию f() в качестве параметра. Функция f() запускается на каждом элементе e списка до тех пор, пока f(e) не вернет True. Тогда list_func_index() возвращает индекс e.

Codewise:

>>> def list_func_index(lst, func):
      for i in range(len(lst)):
        if func(lst[i]):
          return i
      raise ValueError('no element making func True')

>>> l = [8,10,4,5,7]
>>> def is_odd(x): return x % 2 != 0
>>> list_func_index(l,is_odd)
3

Есть ли более элегантное решение? (и лучшее название для функции)

Ответы [ 6 ]

79 голосов
/ 09 ноября 2009

Вы можете сделать это в одну строку, используя генераторы:

next(i for i,v in enumerate(l) if is_odd(v))

Хорошая особенность генераторов в том, что они вычисляют только до запрошенной суммы. Поэтому запросить первые два индекса (почти) так же просто:

y = (i for i,v in enumerate(l) if is_odd(v))
x1 = next(y)
x2 = next(y)

Впрочем, ожидайте исключение StopIteration после последнего индекса (именно так работают генераторы). Это также удобно в подходе «сначала возьми», чтобы знать, что такого значения не найдено - функция list.index () выдаст здесь ValueError.

12 голосов
/ 09 ноября 2009

@ Принятый Павлом лучший ответ, но вот немного латерального мышления, в основном для развлечения и обучения ...:

>>> class X(object):
...   def __init__(self, pred): self.pred = pred
...   def __eq__(self, other): return self.pred(other)
... 
>>> l = [8,10,4,5,7]
>>> def is_odd(x): return x % 2 != 0
... 
>>> l.index(X(is_odd))
3

По сути, цель X состоит в том, чтобы изменить значение «равенства» с нормального на «удовлетворяет этому предикату», что позволяет использовать предикаты во всех ситуациях, которые определены как проверка на равенство - - например, он также позволит вам вместо if any(is_odd(x) for x in l): кодировать, более короткий if X(is_odd) in l: и т. д.

Стоит ли использовать? Не тогда, когда более явный подход, подобный @Paul, столь же удобен (особенно когда его заменяют на использование новой, блестящей встроенной функции next, а не на старый, менее подходящий метод .next, как я предлагаю в комментарий к этому ответу), но есть и другие ситуации, в которых он (или другие варианты идеи «подправить значение равенства», а может быть, и другие компараторы и / или хеширование) могут быть уместными. Главным образом, стоит знать об этой идее, чтобы однажды не придумывать ее с нуля; -).

11 голосов
/ 09 ноября 2009

Одна возможность - встроенная функция enumerate :

def index_of_first(lst, pred):
    for i,v in enumerate(lst):
        if pred(v):
            return i
    return None

Типично называть функцию, подобную той, которую вы описываете, как «предикат»; он возвращает истину или ложь для какого-то вопроса. Вот почему я называю это pred в моем примере.

Я также думаю, что было бы лучше вернуть None, так как это реальный ответ на вопрос. Вызывающий абонент может взорваться на None, если требуется.

4 голосов
/ 09 ноября 2009

Не одна функция, но вы можете сделать это довольно легко:

>>> test = lambda c: c == 'x'
>>> data = ['a', 'b', 'c', 'x', 'y', 'z', 'x']
>>> map(test, data).index(True)
3
>>>

Если вы не хотите оценивать весь список сразу, вы можете использовать itertools, но это не так красиво:

>>> from itertools import imap, ifilter
>>> from operator import itemgetter
>>> test = lambda c: c == 'x'
>>> data = ['a', 'b', 'c', 'x', 'y', 'z']
>>> ifilter(itemgetter(1), enumerate(imap(test, data))).next()[0]
3
>>> 

Просто использование выражения генератора, вероятно, более читабельно, чем itertools.

Примечание в Python3, map и filter возвращают ленивые итераторы, и вы можете просто использовать:

from operator import itemgetter
test = lambda c: c == 'x'
data = ['a', 'b', 'c', 'x', 'y', 'z']
next(filter(itemgetter(1), enumerate(map(test, data))))[0]  # 3
2 голосов
/ 26 июня 2012

Вариант ответа Алекса. Это позволяет избежать необходимости вводить X каждый раз, когда вы хотите использовать is_odd или любой другой предикат

>>> class X(object):
...     def __init__(self, pred): self.pred = pred
...     def __eq__(self, other): return self.pred(other)
... 
>>> L = [8,10,4,5,7]
>>> is_odd = X(lambda x: x%2 != 0)
>>> L.index(is_odd)
3
>>> less_than_six = X(lambda x: x<6)
>>> L.index(less_than_six)
2
1 голос
/ 09 ноября 2009

вы можете сделать это с помощью списка-понимания:

l = [8,10,4,5,7]
filterl = [a for a in l if a % 2 != 0]

Тогда filterl вернет всем членам списка, выполняющим выражение a% 2! = 0. Я бы сказал, более элегантный метод ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...