Метка: задача

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

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

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 👈 

Задача на ключи словаря

Имеется такой код, где мы делаем 5 записей в словарь:

d = {}

d[float('nan')] = 1
d[float('nan')] = 2
d[1.0] = 'float'
d[1] = 'int'
d[True] = 'bool'

print(len(d))

Давайте решим ее. Для ключа словаря нам важны две вещи:

  • Хэш hash(key) – ключи с разными хэшами дадут нам разные записи в словаре.
  • Равенство ключей – если хэши равны, то проверяется равенство ключей (==), и если и они равны, то считается, что ключ тот же самый – это будет одна и та же запись в словаре.

float(‘nan’)

float('nan') – создает нам новый объект типа float со значением NaN (not a number – не число). Это специально значение. Оно получается, если результат операции не определен. Например, вычитание бесконечности из бесконечности не даст нам конкретного определенного результата, потому что бесконечность – это не число:

>>> print(float('Inf') - float('Inf'))
nan

В соответствии с IEEE 754, такое состояние задаётся через установку показателя степени в зарезервированное значение 11…11, а мантиссы — во что угодно, кроме 0 (зарезервированное значение для машинной бесконечности).

У NaN есть замечательно свойство, что он не равен никакому другому float, даже самому себе или другому NaN.

>>> x = float('nan')
>>> x == x
False
>>> hash(x)
0

Но hash от NaN всегда равен 0. Таким образом, словарь видит, что мы кладем в него ключи с одинаковым хэшем, но не равные между собой. Вывод: мы можем создать сколько угодно ключей с NaN, на вид они одинаковые, даже побитово могут совпадать, но так как каждый NaN не равен другому NaN по определению, то и dict все их считает разными!

>>> d = {}
>>> d[float('nan')] = 1
>>> d[float('nan')] = 2
>>> d
{nan: 1, nan: 2}

>>> d[float('nan')]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: nan

1, 1.0 и True

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

>>> hash(1), hash(1.0), hash(True)
(1, 1, 1)
>>> 1 == 1.0 == True
True

Все они равны между собой! Поэтому в словаре все эти три ключа будут отвечать ровно одной записи! Посмотрите:

>>> d = {}

>>> d[1.0] = 'float'
>>> d[1] = 'int'
>>> d[True] = 'bool'

>>> d
{1.0: 'bool'}
>>> len(d)
1

>>> d[1]
'bool'
>>> d[1.0]
'bool'
>>> d[True]
'bool'

Так как первая запись была с 1.0, то и ключ останется типа float, а значение уже будет перезаписано будущими операторами присваивания.

Ответ: 3

У нас в словаре две записи от разных float('nan') и только одна запись от трех присваиваний 1.0, 1 и True. Итого ответ – 3 (три) записи будет в словаре!

Пусть вас не путает, что в условии задачи было 5 операторов.

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