Метка: поиск

Найти первый элемент списка по условию

Пускай имеется такая задача: дан список с численными элементами. Требуется найти и вернуть первый отрицательный элемент. Казалось бы, должна быть какая-нибудь встроенная функция для этого, но нет. Придется писать ее самим. Решение в лоб:

items = [1, 3, 5, -17, 20, 3, -6]

for x in items:
    if x < 0:
        print(x)
        break
else:
    print('not found')

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

result = list(filter(lambda x: x < 0, items))[0]
print(result)

По-моему, стало гораздо сложнее, хоть и в одну строку. А может лучше так:

result = [x for x in items if x < 0][0]

Что ж, теперь лаконичнее, но все равно не идеал. Какая самая большая ошибка здесь? Что в первом, что во втором случае идет перебор всего итератора до конца, а потом отбрасываются все лишние значения, кроме нулевого индекса. Тогда как изначальный код останавливается, найдя нужно значение, экономя и время, и память.

Правильное решение

Лучше использовать встроенную функцию next – она возвращает следующий элемент из итератора, а в качестве итератора мы напишем генераторное выражение с if. Вот так:

result = next(x for x in items if x < 0)

Вот это коротко, экономно и очень по-питоновски (in a pythonic way). Остается одна проблемка: если элемент не найден, что будет брошено исключение StopIteration. Чтобы подавить его, достаточно вторым аргументом в next передать значение по-умолчанию. Если оно задано, то оно будет возвращено вместо возбуждения исключения, если в итераторе нет элементов, то есть не найдено удовлетворяющих условию элементов в исходной коллекции. И не забыть обернуть генераторное выражение в скобки:

items = [1, 2, 4]
result = next((x for x in items if x < 0), 'not found')
print(result)  # not found

С произвольной функцией, задающей критерий поиска (ее еще называют предикат – predicate) это выглядит так:

def is_odd(x):
    return x % 2 != 0

result = next(x for x in items if is_odd(x))
# или еще лучше
result = next(filter(is_odd, items))

Так как в Python 3 filter работает лениво, как и генератор, она не «обналичивает» весь исходный список через фильтр, а лишь идет до первого удачно-выполненного условия. Любите итераторы! ✌️ 

Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈