Подборка решений для задач с собеседований Python-разработчиков
Проверка на палиндром
Палиндром — это последовательность символов, которая одинаково читается в обоих направлениях.
1. Метод с использованием индексации
Первый и самый простой способ проверить, является ли строка палиндромом, — использовать трюк с индексацией для получения «зеркальной» строки и сравнения её с исходной.
s = "radar"
if s == s[::-1]:
print("Это палиндром")
else:
print("Это не палиндром")
2. Метод с использованием цикла
Мы можем также использовать цикл для определения палиндрома, сравнивая символы на симметричных позициях.
s = "radar"
is_palindrome = True
for i in range(len(s) // 2):
if s[i] != s[-i - 1]:
is_palindrome = False
break
if is_palindrome:
print("Это палиндром")
else:
print("Это не палиндром")
3. Использование функции
Проверку на палиндром можно также реализовать с помощью рекурсивной функции.
def is_palindrome(s):
if len(s) < 2:
return True
if s[0] != s[-1]:
return False
return is_palindrome(s[1:-1])
4. Определение палиндрома с учетом пробелов и знаков препинания
Если вы хотите проверить, является ли строка палиндромом, игнорируя пробелы, знаки препинания и регистр, вы можете использовать методы lower()
и replace()
, а также функцию isalnum()
для удаления всех небуквенных символов.
s = "A man, a plan, a canal: Panama"
s = ''.join(c for c in s if c.isalnum()).lower()
if s == s[::-1]:
print("Это палиндром")
else:
print("Это не палиндром")
5. Использование стека
Этот подход использует структуру данных «стек», чтобы сохранить первую половину строки и затем сравнить ее с обратной половиной.
def is_palindrome(s):
s = ''.join(c for c in s if c.isalnum()).lower()
stack = list()
for character in s:
stack.append(character)
for character in s:
if character != stack.pop():
return False
return True
Алгоритмы для поиска палиндромов
Алгоритм поиска самой длинной подстроки-палиндрома
АТАТА: распутываем задачу про палиндром
Проверка на анаграмму
Анаграмма — это перестановка букв одного слова для получения нового слова.
1. Решение без оптимизации
Для начала рассмотрим простейший способ. Для каждой строки мы создадим словарь, который будет хранить количество вхождений каждого символа. Затем мы сравним словари и выясним, являются ли строки анаграммами.
Вот как может выглядеть код для реализации данного алгоритма:
def is_anagram(str1, str2):
# создаем словарь для первого слова
dict1 = {}
for char in str1:
dict1[char] = dict1.get(char, 0) + 1
# создаем словарь для второго слова
dict2 = {}
for char in str2:
dict2[char] = dict2.get(char, 0) + 1
# сравниваем словари
return dict1 == dict2
На этом решение задачи поиска анаграмм без оптимизации можно считать выполненной. Однако этот алгоритм не оптимален.
2. Оптимизация алгоритма
Можно заметить, что мы используем два словаря для каждого слова. Это может быть неэффективно, особенно, если слова достаточно длинные.
Вместо хранения двух словарей мы можем использовать один словарь и удалять из него ключи, которые мы уже нашли. После этого мы снова сравниваем словари. Если они равны, значит слова являются анаграммами.
Вот как может выглядеть оптимизированный код:
def is_anagram(str1, str2):
# создаем словарь для первого слова
dict1 = {}
for char in str1:
dict1[char] = dict1.get(char, 0) + 1
# проверяем второе слово
for char in str2:
if char in dict1:
dict1[char] -= 1
if dict1[char] == 0:
del dict1[char]
else:
return False
# если словарь пуст, значит слова являются анаграммами
return not dict1
Теперь код выглядит более эффективно, но он все еще имеет недостатки.
3. Более сложная оптимизация
Работа с словарями может быть медленной, особенно если слова достаточно длинные. Мы можем использовать более продвинутые алгоритмы, чтобы ускорить этот процесс.
Один из таких алгоритмов — это использование массивов. Мы можем создать два массива фиксированной длины, которые будут хранить количество букв в каждом слове. Затем мы можем проходить по каждой букве в обоих словах и увеличивать счетчики в массиве.
По завершении прохода мы можем сравнить массивы. Если они равны, значит слова являются анаграммами.
Вот как может выглядеть код для этого алгоритма:
def is_anagram(str1, str2):
# создаем массивы
count1 = [0] * 26
count2 = [0] * 26
# заполняем массивы
for char in str1:
index = ord(char) - ord('a')
count1[index] += 1
for char in str2:
index = ord(char) - ord('a')
count2[index] += 1
# сравниваем массивы
return count1 == count2
Поиск анаграмм и сабанаграмм во всех словах языка
Сжатие строки (rle)
Задача:
Необходимо реализовать функцию, принимающую в аргументах строку, состоящую из букв, и возвращающую новую строку, в которой повторяющиеся буквы заменены количеством повторений.
# AAAABBCAA --> A4B2C1A2
Решение:
def encode_message(message):
encoded_string = ""
i = 0
while (i <= len(message)-1):
count = 1
ch = message[i]
j = i
while (j < len(message)-1):
if (message[j] == message[j + 1]):
count = count + 1
j = j + 1
else:
break
encoded_string = encoded_string + str(count) + ch
i = j + 1
return encoded_string
Приведенный фрагмент содержит функцию encode_message()
с параметром message
(строка), подлежащим кодированию.
Переменная encoded_string
используется для хранения закодированной строки. Цикл while
инициализируется с счетчиком с значением 1 и итерируется по всем символам строки. Внутренний цикл while
проверяет, совпадает ли символ текущего индекса с символом следующего индекса.
Если символы совпадают, то счётчик инкрементируется. Если нет, то счетчик и символ конкатенируются в переменную encoded_string
и возвращаются.
Алгоритм имеет сложность O (n)
.
Также можно воспользоваться функцией groupby
из модуля itertools
. Она превратит последовательность элементов в последовательность групп повторяющихся символов.
from itertools import groupby
def rle_encode(data: str) -> str:
return "".join(str(len(list(group_items))) + key
for key, group_items in groupby(data))
Простейшие алгоритмы сжатия: RLE и LZ77
Подсчёт гласных в строке
Первый способ — использование цикла for
string = "Привет, мир!"
count = 0
for char in string:
if char in "аеёиоуыэюяАЕЁИОУЫЭЮЯ":
count += 1
print("Количество гласных букв:", count)
#Количество гласных букв: 3
Второй способ — использование метода count()
# Введите строку
input_string = "Пример строки на русском языке"
# Преобразуем строку к нижнему регистру, чтобы учитывать гласные в верхнем и нижнем регистре
input_string = input_string.lower()
# Определите гласные для русского языка
vowels = "аеёиоуыэюя"
# Используйте sum() и count() для подсчета количества каждой гласной
vowel_count = sum(input_string.count(vowel) for vowel in vowels)
# Выведите результат
print(f"Количество гласных в строке: {vowel_count}")
#Количество гласных в строке: 10
Третий способ — использование регулярных выражений
import re
string = "Привет, мир!"
count = len(re.findall("[аеёиоуыэюяАЕЁИОУЫЭЮЯ]", string))
print("Количество гласных букв:", count)
#Количество гласных букв: 3
Четвертый способ — использование генераторов списков
string = "Привет, мир!"
vowels = [char for char in string if char in "аеёиоуыэюяАЕЁИОУЫЭЮЯ"]
count = len(vowels)
print("Количество гласных букв:", count)
#Количество гласных букв: 3
Бинарный поиск
Бинарный поиск — это алгоритм, используемый для поиска элемента в отсортированном массиве путем многократного деления интервала поиска пополам.
Принцип алгоритма бинарного поиска очень прост. Мы всегда имеем дело с упорядоченным массивом. Это позволяет прибегать к хитрости - на каждой итерации цикла, который мы запускаем, мы вычисляем индекс среднего элемента. Этот элемент сравниваем с искомым.
Если элемент из середины массива оказался меньше, чем тот, что мы ищем, значит нужный нам находится правее по массиву, ведь массив упорядочен. Соответственно, нам нужно перейти в следующую итерацию цикла, "оставляя" ей лишь правую часть массива - в ней снова будет найден средний элемент и алгоритм повторится.
Если же элемент из середины массива оказался больше искомого, то мы переходим к следующей итерации, отбрасывая правую часть массива, а оставляя левую.
Если же элемент совпадает с искомым, мы выходим из цикла.
def BinarySearch(lys, val):
first = 0
last = len(lys)-1
index = -1
while (first <= last) and (index == -1):
mid = (first+last)//2
if lys[mid] == val:
index = mid
else:
if val<lys[mid]:
last = mid -1
else:
first = mid +1
return index
Также, вместо цикла мы могли бы обратиться к рекурсии.
Я не могу написать бинарный поиск
Проверка симметричности двоичного дерева
Постановка:
Напишите функцию для проверки симметричности двоичного дерева. Примеры деревьев:
Дерево 1:
1
/ \
/ \
2 2
/ \ / \
3 4 4 3
Дерево 2:
1
/ \
/ \
2 2
/ \ / \
3 5 6 3
Дерево 3:
1
/ \
/ \
2 2
/ \
5 5
Первое и третье деревья симметричны, а второе – нет.
Решение:
В соответствии с условием симметричным деревом считается дерево, которое симметрично и с точки зрения структуры, и с точки зрения значений узлов. Чтобы проверить дерево на симметричность, можно создать его зеркальное отражение и сравнить его с оригиналом. Временная и пространственная сложность такого алгоритма – O(n), где n
– число узлов. Проверку можно оптимизировать, если вместо создания отражения целого дерева сравнивать пары поддеревьев – временная сложность такого подхода O(n), а пространственная – O(h), где h
– высота дерева:
def is_tree_symmetric(tree):
def check_symmetric(subtree_0, subtree_1):
if not subtree_0 and not subtree_1:
return True
elif subtree_0 and subtree_1:
return (subtree_0.data == subtree_1.data
and check_symmetric(subtree_0.left, subtree_1.right)
and check_symmetric(subtree_0.right, subtree_1.left))
return False
return not tree or check_symmetric(tree.left, tree.right)
Пример использования с заданными в условии деревьями:
from collections import namedtuple
Node = namedtuple('Node', ['data', 'left', 'right'])
tree1 = Node(1, Node(2, Node(3, None, None), Node(4, None, None)), Node(2, Node(4, None, None), Node(3, None, None)))
tree2 = Node(1, Node(2, Node(3, None, None), Node(5, None, None)), Node(2, Node(6, None, None), Node(3, None, None)))
tree3 = Node(1, Node(2, Node(5, None, None), None), Node(2, None, Node(5, None, None)))
for t in [tree1, tree2, tree3]:
print(is_tree_symmetric(t))
Вывод:
True
False
True
Структуры данных: бинарные деревья. Часть 1
Бинарные деревья поиска и рекурсия – это просто
Метод двух указателей
Метод двух указателей является эффективным подходом для решения ряда задач на Python и не только. Он заключается в использовании двух указателей, которые движутся по структуре данных в разных направлениях или с разной скоростью.
Одним из классических примеров использования метода двух указателей является задача о нахождении пары чисел в упорядоченном массиве, сумма которых равна заданному числу. Для её решения можно использовать следующий алгоритм:
left, right = 0, len(nums) - 1
while left < right:
if nums[left] + nums[right] == target:
return [left + 1, right + 1]
elif nums[left] + nums[right] < target:
left += 1
else:
right -= 1
В этом примере указатели left
и right
сначала указывают на крайние элементы массива, а затем двигаются в стороны, пока не будет найдена пара, сумма элементов которой равна заданному числу.
Если сумма элементов меньше искомого числа, то left
увеличивается на 1, иначе уменьшается на 1. Если указатели встречаются, значит, решение не найдено, и можно вернуть None или другой флаг.
Кроме этой задачи, метод двух указателей может быть применен, например, для поиска подстроки в строке или для проверки, является ли строка палиндромом.
Мемоизация и факториал
Мемоизация - это метод, используемый для хранения результатов предыдущих вызовов функций для ускорения будущих вычислений. Если повторные вызовы функций выполняются с одинаковыми параметрами, мы можем сохранить предыдущие значения вместо повторения ненужных вычислений.
Пример будет базироваться на применении мемоизации для оптимизиации вычисления факториала.
Рекурсивная функция, вычисляющая факториал, может выглядеть так:
def factorial(input_value):
if input_value < 2:
return 1
elif input_value >= 2:
return input_value * factorial(input_value - 1)
Теперь попробуем многократно воспользоваться этой функцией с помощью цикла:
for i in range(1, 11):
print(f"{i}! = ", factorial(i))
Функция отрабатывает замечательно, но если мы увеличим количество итераций цикла до 5000, то выполнение скрипта прервётся с ошибкой "RecursionError: maximum recursion depth exceeded in comparison"
. Это происходит потому, что чем больше переданное в функцию число, тем большее количество операций ей необходимо совершить. При этом, на каждой итерации мы заново вычисляем факториал для значений, расчёт которых уже происходил на предшествующих итерациях. Здесь-то и нужна мемоизация, чтобы обеспечить трату ресурсов только на те вычисления, которые еще не производились.
Теперь давайте пройдемся по этапам реализации метода мемоизации. Чтобы продолжить, давайте инициализируем словарь:
factorial_dict = {}
Далее мы определим нашу функцию с применением мемоизации. Сначала мы проверяем, меньше ли введенное значение 2, и возвращаем 1, если это так:
def factorial_memo(input_value):
if input_value < 2:
return 1
Затем мы проверяем, есть ли входное значение в словаре. Если это не так, мы сохраняем значение факториала в словаре и возвращаем значение для ключа ввода. Полная функция выглядит так:
def factorial_memo(input_value):
if input_value < 2:
return 1
if input_value not in factorial_dict:
factorial_dict[input_value] = input_value * factorial_memo(input_value - 1)
return factorial_dict[input_value]
Эта функция уже способна осилить наш цикл в 5000 итераций, но мы могли бы поступить иначе.
А именно, импортировать декоратор из модуля functools
, называемого lru_cache
, который позволяет нам кэшировать наши значения. Мы можем достичь той же производительности, что и с нашим методом factorial_memo
, используя этот декоратор:
from functools import lru_cache
def factorial(input_value):
if input_value < 2:
return 1
elif input_value >= 2:
return input_value * factorial(input_value - 1)
Алгоритмы быстрого вычисления факториала
Последовательность Фибоначчи
Классическая задача, которую можно встретить на собеседованиях самого разного уровня. Стоит напомнить, что последовательность Фибоначчи — это ряд чисел, где каждое последующее является суммой двух предыдущих. Так, первые десять чисел выглядят следующим образом: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
.
Замкнутая формула
from __future__ import division
import math
def fib(n):
SQRT5 = math.sqrt(5)
PHI = (SQRT5 + 1) / 2
return int(PHI ** n / SQRT5 + 0.5)
Плюсы:
- Быстро и просто для малых n
Минусы:
- Требуются операции с плавающей запятой, для больших n
потребуется большая точность
- Использование комплексных чисел для вычисления Fn красиво с математической точки зрения, но уродливо — с компьютерной
Рекурсия
fib = lambda n: fib(n - 1) + fib(n - 2) if n > 2 else 1
Плюсы:
- Очень простая реализация, повторяющая математическое определение
Минусы:
- Экспоненциальное время выполнения, для больших n
очень медленно
- Переполнение стека
Мемоизация
M = {0: 0, 1: 1}
def fib(n):
if n in M:
return M[n]
M[n] = fib(n - 1) + fib(n - 2)
return M[n]
Плюсы:
- Просто превратить рекурсию в решение с запоминанием; превращает экспоненциальное время выполнение в линейное, для чего тратит больше памяти
Минусы:
- Тратит много памяти
- Возможно переполнение стека, как и у рекурсии
Динамическое программирование
def fib(n):
a = 0
b = 1
for __ in range(n):
a, b = b, a + b
return a
Плюсы:
- Быстро работает для малых n
, простой код
Минусы:
- Всё ещё линейное время выполнения
Матричная алгебра
def pow(x, n, I, mult):
"""
Возвращает x в степени n. Предполагает, что I – это единичная матрица, которая
перемножается с mult, а n – положительное целое
"""
if n == 0:
return I
elif n == 1:
return x
else:
y = pow(x, n // 2, I, mult)
y = mult(y, y)
if n % 2:
y = mult(x, y)
return y
def identity_matrix(n):
"""Возвращает единичную матрицу n на n"""
r = list(range(n))
return [[1 if i == j else 0 for i in r] for j in r]
def matrix_multiply(A, B):
BT = list(zip(*B))
return [[sum(a * b
for a, b in zip(row_a, col_b))
for col_b in BT]
for row_a in A]
def fib(n):
F = pow([[1, 1], [1, 0]], n, identity_matrix(2), matrix_multiply)
return F[0][1]
Плюсы:
- Фиксированный объём памяти, логарифмическое время
Минусы:
- Код посложнее
- Приходится работать с матрицами, хотя они не так уж и плохи
Сравнение быстродействия
Сравнивать стоит только вариант динамического программирования и матрицы. Если сравнивать их по количеству знаков в числе n
, то получится, что матричное решение линейно, а решение с динамическим программированием – экспоненциально. Практический пример – вычисление fib(10 ** 6)
, числа, у которого будет больше двухсот тысяч знаков.
n = 10 ** 6
Вычисляем fib_matrix
: у fib(n)
всего 208988 цифр, расчёт занял 0.24993 секунд.
Вычисляем fib_dynamic
: у fib(n)
всего 208988 цифр, расчёт занял 11.83377 секунд.
Фибоначчи на собеседовании
Трюк из линейной алгебры для быстрого нахождения чисел Фибоначчи
Распаковка вложенных списков неопределенной глубины
Задача
Напишите функцию flatten
, которая будет принимать на вход список с любой вложенностью (список в списке), а возвращать список, где вложенные элементы всех уровней распакованы, то есть вложенное превращается в плоское.
Варианты решения:
Вариант 1:
def flatten(array: Iterable) -> List:
new_array = array[:]
ind = 0
while True:
try:
while isinstance(new_array[ind], list):
item = new_array.pop(ind)
for inner_item in reversed(item):
new_array.insert(ind, inner_item)
ind += 1
except IndexError:
break
return new_array
Вариант заключается в том, что мы копируем исходный список (чтобы не изменять его), а далее в цикле while
проверяем, пока элемент является списком — проходимся по нему и инсёртим результат в самое начало.
Алгоритм не оптимален т.к. каждый раз происходит переиндексирование (т.к. мы добавляем и удаляем из начала списка).
Сложность данного алгоритма O(n^3*m)
из-за перестройки списка дважды за каждую пройденную итерацию.
Вариант 2:
def flatten(data: Iterable) -> List:
nested = True
while nested:
new = []
nested = False
for i in data:
if isinstance(i, list):
new.extend(i)
nested = True
else:
new.append(i)
data = new
return data
Данный вариант заключается в том, что внутри цикла while nested
идёт перебор по существующему списку с ключом nested = False
, и, в случае, если ему хотя бы раз попался лист, он оставляет флаг nested = True
и extend'ит этот лист в список. Соответственно, получается, что за каждый прогон он раскладывает один уровень вложенности. Сколько будет уровней вложенности — столько будет и проходов по циклу. Как видно из описания — не самый оптимальный алгоритм, но всё же, является отличным от остальных.
Сложность данного алгоритма O(n*m)
.
Вариант 3:
def flatten(a: Iterable) -> List:
queue, out = [a], []
while queue:
elem = queue.pop(-1)
if isinstance(elem, list):
queue.extend(elem)
else:
out.append(elem)
return out[::-1]
Алгоритм является достаточно простым и чистым. Смысл его в том, чтобы в случае, если элемент - это список, мы добавляем результат в конец первоначального списка, а в случае, если нет — добавляем в вывод. Как результат — выводим перевёрнутый полученный результирующий плоский список, чтобы сохранить первоначальный порядок элементов.
Сложность данного алгоритма O(n*m)
.
Вариант 4:
def recursive_flatten_iterator(arr: Iterable) -> Iterator:
for i in arr:
if isinstance(i, list):
yield from recursion_flatten(i)
else:
yield i
Наверное, самый распространённый вариант решения данной задачи — через рекурсию и yield from
. Алгоритм кажется достаточно простым и эффективным, не считая того, что он сделан через рекурсию и, при больших вложенностях, может быть достаточно большой стек вызовов.
Сложность данного алгоритма O(n*m)
.
Вариант 5:
def recursive_flatten_generator(array: Iterable) -> List:
lst = []
for i in array:
if isinstance(i, list):
lst.extend(recursive_flatten_list(i))
else:
lst.append(i)
return lst
Эта функция похожа на предыдущую, только выполнена не через итератор, а через рекурсивный extend/append
механизм.
Сложность данного алгоритма O(n*m)
.
Вариант 6:
def flatten(seq: Iterable) -> List:
stack = [iter(seq)]
new = []
while stack:
i = stack.pop()
try:
while True:
data = next(i)
if isinstance(data, list):
stack.append(i)
i = iter(data)
else:
new.append(data)
except StopIteration:
pass
return new
Сложность данного алгоритма O(n*m)
.
Распаковка вложенных списков неопределенной глубины
Каррирование и частичное применение
Каррирование — это преобразование функции от многих аргументов в набор функций, каждая из которых является функцией от одного аргумента. Мы можем передать часть аргументов в функцию и получить обратно функцию, ожидающую остальные аргументы.
Создадим простую функцию greet
, которая принимает в качестве аргументов приветствие и имя:
def greet(greeting, name):
print(greeting + ', ' + name)
Небольшое улучшение позволит нам создать новую функцию для любого типа приветствия и передать этой новой функции имя:
def greet_curried(greeting):
def greet(name):
print(greeting + ', ' + name)
return greet
greet_hello = greet_curried('Hello')
greet_hello('German')
greet_hello('Ivan')
# или напрямую greet_curried
greet_curried('Hi')('Roma')
А дальше можно сделать это с любым количеством аргументов:
def greet_deeply_curried(greeting):
def w_separator(separator):
def w_emphasis(emphasis):
def w_name(name):
print(greeting + separator + name + emphasis)
return w_name
return w_emphasis
return w_separator
greet = greet_deeply_curried("Hello")("...")(".")
greet('German')
greet('Ivan')
Или вариант с lambda
:
greet_deeply_curried = lambda greeting: lambda separator: lambda emphasis: lambda name: \
print(greeting + separator + name + emphasis)
Частичное применение - это процесс применения функции к части ее аргументов.
Другими словами, функция, которая принимает функцию с несколькими параметрами и возвращает функцию с меньшим количеством параметров.
Частичное применение преобразует функцию от n
аргументов к (x-n)
, а каррирование создает n
функций с 1 аргументом.
Такая возможность есть у Python в стандартной библиотеке functools
, это функция partial
.
from functools import partial
def greet(greeting, separator, emphasis, name):
print(greeting + separator + name + emphasis)
newfunc = partial(greet, greeting='Hello', separator=',', emphasis='.')
newfunc(name='German')
newfunc(name='Ivan')
newfunc2 = partial(greet, greeting='Hello', emphasis='.')
newfunc2(name='German', separator='...')
newfunc2(name='Ivan', separator='..')
Еще один пример с partial
, решает проблему замыкания в цикле:
from functools import partial
def makeActions():
acts = []
for i in range(5):
def func(x, y):
return x * y
acts.append(partial(func, y=i))
# acts.append(partial(lambda x, y: x * y, y=i)) # через lambda
# return [partial(lambda x, y: x * y, y=i) for i in range(5)] # через генератор списка
return acts
for act in makeActions():
print(act(1), end=', ')
Мемоизация и каррирование (Python)
Ненормальное функциональное программирование на python
FizzBuzz
Задача
Напишите программу, печатающую числа от 1 до 100. Вместо чисел кратных трём, программа должна печатать 'Fizz'
. Вместо чисел кратных пяти - 'Buzz'
. Если число кратно и трём, и пяти, программа должна печатать 'FizzBuzz'
.
Решение
Начнём со стандартного, классического решения:
for i in range(1, 101):
if i % 3 == 0 and i % 5 == 0:
print('FizzBuzz')
elif i % 3 == 0:
print('Fizz')
elif i % 5 == 0:
print('Buzz')
else:
print(i)
Укладываем стандартное решение в стандартный однострочник
print('\n'.join('FizzBuzz' if i%3==0 and i%5==0 else 'Fizz' if i%3==0 else 'Buzz' if i%5==0 else str(i) for i in range(1, 101)))
Избавляемся от join
, приручаем print
[print('FizzBuzz' if i%3==0 and i%5==0 else 'Fizz' if i%3==0 else 'Buzz' if i%5==0 else i) for i in range(1, 101)]
Добавляем срез, укрощаем if else
[print('FizzBuzz'[4 if i%3 else 0:4 if i%5 else 8] or i) for i in range(1, 101)]
Оптимизируем срез, избавляемся от if else
[print('FizzBuzz'[i*i%3*4:8--i**4%5] or i) for i in range(1, 101)]
Сократили неплохо. Но, похоже с этой вариацией дальше не продвинуться. Пробуем иной вариант.
Заменяем срез конкатенацией, вертаем оператор modulo
[print('Fizz'*(i%3==0)+'Buzz'*(i%5==0) or i) for i in range(1, 101)]
Оптимизируем решение. Выравниваем по длине со срезом
[print((i%3<1)*'Fizz'+(i%5<1)*'Buzz' or i) for i in range(1, 101)]
Уходим в отрыв. Модифицируем окончательный вариант
[print(i%3//2*'Fizz'+i%5//4*'Buzz' or i+1) for i in range(100)]
А что по скорости?
# 2.53 msec
print('\n'.join('FizzBuzz' if i%3==0 and i%5==0 else 'Fizz' if i%3==0 else 'Buzz' if i%5==0 else str(i) for i in range(1, 101)))
# 8.11 msec
[print('FizzBuzz' if i%3==0 and i%5==0 else 'Fizz' if i%3==0 else 'Buzz' if i%5==0 else i) for i in range(1, 101)]
# 8.43 msec
[print('FizzBuzz'[4 if i%3 else 0:4 if i%5 else 8] or i) for i in range(1, 101)]
# 8.31 msec
[print('FizzBuzz'[i*i%3*4:8--i**4%5] or i) for i in range(1, 101)]
# 8.38 msec
[print('Fizz'*(i%3==0)+'Buzz'*(i%5==0) or i) for i in range(1, 101)]
# 8.4 msec
[print((i%3<1)*'Fizz'+(i%5<1)*'Buzz' or i) for i in range(1, 101)]
# 8.49 msec
[print(i%3//2*'Fizz'+i%5//4*'Buzz' or i+1) for i in range(100)]
Решение Fizzbuzz при помощи теоремы Эйлера
Непайтоновый Пайтон
Удалить лишние пробелы
Задача
В предложение были добавлены лишние пробелы. Напишите функцию, которая будет принимать такое предложение и возвращать его же в исправленном виде. Все слова должны быть разделены одним пробелом, а в начале и конце предложения пробелов быть не должно.
Примеры
correct_spacing("The film starts at midnight. ")
# ➞ "The film starts at midnight."
correct_spacing("The waves were crashing on the shore. ")
# ➞ "The waves were crashing on the shore."
correct_spacing(" Always look on the bright side of life.")
# ➞ "Always look on the bright side of life."
Вариант решения
def correct_spacing(sentence):
return ' '.join(sentence.split())