Статьи
June 29, 2022

Числа, логические операторы и не только

Однажды, решая задачи на Codewars.com, наткнулся на интересное решение.

Суть задачи: на входе вводилось целое число, далее нужно было сложить все его цифры. Если получалось не однозначное число, то снова сложить его цифры, и так далее пока не получим однозначное. Его и нужно было вывести.

Пример:

In: 23456798
Out: 8 (23456789 -> 2+3+4+5+6+7+9+8 = 44 -> 4+4 = 8)

Самое короткое решение из предложенных было:

def digital_root(n): 
    return n % 9 or n and 9

Здесь применяется свойство: у суммы цифр любого числа будет такой же остаток при делении на 9, как и у самого числа. Но заинтересовало не это, а именно взаимодействие логических операторов и чисел. Итак, давайте разберем пару примеров.

Множественные сравнения

Мало для кого секрет, что существует форма записи сравнения нескольких чисел в цепочку: a < b < c.

Если условия удовлетворяют сравнению, то получаем True, если нет – False. Казалось бы, причем тут логические операторы? Но если погрузимся вглубь, то станет понятно, что эта запись – не что иное, как сочетание операторов сравнения и логических операторов.

Запись a < b < c равносильна a < b and b < c. Причем операторы сравнения могут быть любыми, а вместо a, b, c могут быть любые объекты, которые сравниваем в любом количестве.

print(10 < 20 < 30)
# True
print(10 < (5 * 3) < 20)
# True
print(10 < sum([1, 10, 3, 2]) < 20)
# True

Всё достаточно ясно и понятно. Но тут можно попасться в ловушку.

Если мы запишем первое выражение как:

print(10 < 20 < 30 == True)
# False

То получим False.

Казалось бы, почему? И тут опять вспоминаем как работают множественные сравнения. Представим запись в виде, в каком ее обрабатывает Python.

10 < 20 < 30 == True равносильно 10 < 20 and 20 < 30 and 30 == True.

И если в первых двух сравнения мы получим True, то при сравнении 30 == True мы получаем False (тип boolean является подклассом integer в Python и, при сравнении int и bool, True принимает значение 1, а False0).

Не путать с bool(число)! – здесь любое ненулевое число выдаст True.

Если нам нужно сохранить данный формат записи и получить корректный ответ, то на помощь приходят скобки:

print((10 < 20 < 30) == True)
# True

Что равносильно: (10 < 20 and 20 < 30) == True.

Вроде разница между (10 < 20 < 30) == True и 10 < 20 < 30 == True неочевидная, но результаты получаем противоположные, что может нам испортить, как минимум, настроение, когда получим не то, что ожидали.

Числа и логические операторы

Вернемся к нашей задаче.

def digital_root(n):
    return n % 9 or n and 9

Что же означает запись n % 9 or n and 9? Чтобы ответить на этот вопрос, разберемся, как работают логические операторы or, and и not в Python:

  • x or y: Если x ложно, возвращает y, иначе возвращает x
  • x and y: Если x ложно, возвращает x, иначе возвращает y
  • not x: Если x ложно, возвращает True, иначе возвращает False

Действительно:

  • False or True вернет True, так как первый аргумент ложный, возвращает 2й.
  • True or False вернет True, так как первый аргумент не ложный, возвращает его.
  • True or True вернет True, так как первый аргумент не ложный, возвращает его.
  • False or False вернет False, так как первый аргумент ложный, возвращает 2й.
  • False and True вернет False, так как первый аргумент ложный, возвращает его.
  • True and False вернет False, так как первый аргумент не ложный, возвращает второй.
  • True and True вернет True, так как первый аргумент не ложный, возвращает 2й.
  • False and False вернет False, так как первый аргумент ложный, возвращает его.

С not x, думаю, все и так понятно.

Вот мы и подошли к ответу на наш вопрос. Логические операторы работают подобным образом не только с булевыми переменными, но и с любыми другими объектами:

print(0 and 5)
# 0
print(9 and 5)
# 5
print(5 or [])
# 5
print(5 and [])
# []
print(() or {})
# {}
print(None or [1, 2, 3, 4][1:3])
# [2, 3]

Итак, что вернет ввод n % 9 or n and 9?

Если число кратно 9 (т.е. имеет остаток 0 при делении на 9), то:

  • n and 9 вернет 9 при ненулевом n.
  • 0 or 9 вернет 9

18369 -> 27 -> 9 (число 9 и так однозначное, поэтому на не надо дальше получать остаток от деления на 9).

Если число не кратно 9, то:

  • n and 9 вернет 9 при ненулевом n.
  • n % 9 or 9 вернет n % 9, так как остаток ненулевой.

32054 -> 14 -> 5. Действительно, 32054 % 9 = 5.

Вот так ёмко и элегантно можно решить задачу в одну строку без циклов, больших ветвей условий и рекурсий, если знать математические правила и как работают логические операторы в Python.

Автор: Руслан Шакиров

👉🏻Подписывайтесь на PythonTalk в Telegram 👈🏻

👨🏻‍💻Чат PythonTalk в Telegram💬

🍩 Поддержать канал 🫶