Coding
Coding⚑
Як працюють and та or [💡10/100]⚑
Що виведе код?
def func1():
print(1)
def func2():
print(2)
result1 = func1() and func2() # 1
result2 = 0 or '5'
print(result1) # None
print(result2) # 5
FizzBuzz⚑
You are given a list of integers. Your task is to do the following:
- Replace all integers that are evenly divisible by
3with"fizz" - Replace all integers divisible by
5with"buzz" - Replace all integers divisible by both
3and5with"fizzbuzz"
>>> numbers = [45, 22, 14, 65, 97, 72]
>>> for i, num in enumerate(numbers):
... if num % 3 == 0 and num % 5 == 0:
... numbers[i] = 'fizzbuzz'
... elif num % 3 == 0:
... numbers[i] = 'fizz'
... elif num % 5 == 0:
... numbers[i] = 'buzz'
...
>>> numbers
['fizzbuzz', 22, 14, 'buzz', 97, 'fizz']
Вивести найбільші 2 числа для словника⚑
Вивести значення ключів для двох найбільших значень в словнику.
given_dict = {
'a': 40,
'b': 99,
'c': 10,
'd': 200,
}
def find_max_values(dictionary: dict) -> Tuple[int, int]:
if len(dictionary) < 2:
raise ValueError("At least 2 values are required")
if len(dictionary) == 2:
return tuple(dictionary.keys())
max_key = None
second_max_key = None
max_value_1 = float('-inf')
max_value_2 = float('-inf')
for key, value in dictionary.items():
if value > max_value_1:
second_max_key = max_key
max_value_2 = max_value_1
max_key = key
max_value_1 = value
elif value > max_value_2:
second_max_key = key
max_value_2 = value
return max_key, second_max_key
if __name__ == "__main__":
print(find_max_values(given_dict))
Функція перевірки простого числа⚑
import math
def is_prime(number):
if number <= 1:
return False
if number <= 3:
return True
sqrt_num = int(math.sqrt(number)) + 1
for divisor in range(2, sqrt_num):
if number % divisor == 0:
return False
return True
Функція перевірки, чи рядок є паліндромом⚑
Генератор для чисел Фібоначчі⚑
def fibonacci_generator():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib_gen = fibonacci_generator()
for _ in range(10):
print(next(fib_gen))
Рекурсивна функція для чисел Фібоначчі⚑
def fibonacci(n):
if n < 2: # Base case
return n
else:
return fibonacci(n - 1) + fibonacci(n - 2) # Recursive case
n = 10 # Set your desired Fibonacci number here
result = fibonacci(n)
print(f"The Fibonacci number at position {n} is {result}.")
Рекурсивна функція для чисел Фібоначчі з кешем⚑
- Зовнішній словник як кеш
cache = {}
def fibonacci(n: int):
if n < 2: # Base case
return n
if n in cache:
return cache[n]
cache[n] = fibonacci(n - 1) + fibonacci(n - 2) # Compute and cache the Fibonacci number
return cache[n]
n = 10 # Set your desired Fibonacci number here
result = fibonacci(n)
print(f"The Fibonacci number at position {n} is {result}.")
- Декоратор для кешу
def memoize(f):
cache = {}
def decorated_function(*args):
if args in cache:
return cache[args]
else:
cache[args] = f(*args)
return cache[args]
return decorated_function
@memoize
def fibonacci(n):
if n < 2: # Base case
return n
else:
return fibonacci(n - 1) + fibonacci(n - 2)
n = 10 # Set your desired Fibonacci number here
result = fibonacci(n)
print(f"The Fibonacci number at position {n} is {result}.")
Декоратор для заміру часу виконання функції⚑
import functools
import time
def measure_execution_time(function):
@functools.wraps(function)
def wrapper(*args, **kwargs):
start = time.time()
result = function(*args, **kwargs)
end = time.time()
execution_time = end - start
print(f"Execution time of function '{function.__name__}': {execution_time} seconds")
return result
return wrapper
@measure_execution_time
def sample_function():
pass
Декоратора, який буде перехоплювати помилки, і повторіть функцію максимум N раз⚑
from functools import wraps
def retry_on_exception(max_retries):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
attempts = 0
while attempts < max_retries:
try:
return func(*args, **kwargs)
except Exception as e:
print(f"Attempt {attempts + 1} failed with error: {e}. Retrying...")
attempts += 1
raise Exception(f"Function failed after {max_retries} attempts")
return wrapper
return decorator
@retry_on_exception(max_retries=3)
def potentially_failing_function():
print("Executing function")
raise ValueError("An error occurred")
try:
potentially_failing_function()
except Exception as e:
print(f"Function failed with error: {e}")
Функція, яка знаходить найбільше число у вкладеному списку⚑
def find_maximum(lst):
maximum = float('-inf')
for element in lst:
if isinstance(element, (int, float)):
maximum = max(maximum, element)
elif isinstance(element, list):
maximum = max(maximum, find_maximum(element))
return maximum
nested_list = [1, [2, 3], [4, [5, 6], 7], 8, 9]
result = find_maximum(nested_list)
print(f"The maximum in the nested list is: {result}")
Функція, яка знаходить суму усіх чисел у вкладеному списку⚑
def find_sum(numbers_list):
total_sum = 0
for element in numbers_list:
if isinstance(element, list):
total_sum += find_sum(element)
elif isinstance(element, (int, float)):
total_sum += element
return total_sum
nested_list = [1, [2, 3], [4, [5, 6], 7], 8, 9]
sum_result = find_sum(nested_list)
print(f"The sum of all numbers in the nested list is: {sum_result}")
Аналог deepcopy для tree⚑
from typing import Any, List
class Tree:
def __init__(self, data: Any, children: List['Tree'] = None):
self.data = data
self.children = children or []
def custom_deepcopy(tree: Tree) -> Tree:
if not tree:
return None
new_root = Tree(tree.data)
new_root.children = [custom_deepcopy(child) for child in tree.children]
return new_root
def print_tree(tree: Tree, level: int = 0):
if tree:
line = ''
if level >= 2:
line = '│ ' + ' ' * (level - 2)
if level:
line += '└─ '
print(line + str(tree.data))
for child in tree.children:
print_tree(child, level + 1)
root = Tree(1)
child1 = Tree(2)
child2 = Tree(3)
root.children = [child1, child2]
child1.children = [Tree(4), Tree(5)]
child2.children = [Tree(6)]
print("Original tree:")
print_tree(root) # Checking the original tree
copied_tree = custom_deepcopy(root) # Copying the tree
print("\nCopied tree:")
print_tree(copied_tree) # Checking the copied tree
Спискові і словникові вирази [💡24/100]⚑
A0 = dict(zip(('a', 'b', 'c', 'd', 'e'), (1, 2, 3, 4, 5)))
A1 = range(10)
A2 = sorted([i for i in A1 if i in A0])
A3 = sorted([A0[s] for s in A0])
A4 = [i for i in A1 if i in A3]
A5 = {i: i * i for i in A1}
A6 = [[i, i * i] for i in A1]
A7 = [i if i % 2 else 0 for i in A1 if 2 < i < 8]
A8 = ','.join(str(j ** 2) for j in range(10))
---
A0 = {'a': 1, 'c': 3, 'b': 2, 'e': 5, 'd': 4}
A1 = range(0, 10)
A2 = []
A3 = [1, 2, 3, 4, 5]
A4 = [1, 2, 3, 4, 5]
A5 = {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
A6 = [[0, 0], [1, 1], [2, 4], [3, 9], [4, 16], [5, 25], [6, 36], [7, 49], [8, 64], [9, 81]]
A7 = [3, 0, 5, 0, 7]
A8 = '0,1,4,9,16,25,36,49,64,81'
Розташувати функції в порядку зростання часу виконання⚑
def f1(lst):
l1 = sorted(lst)
l2 = [i for i in l1 if i < 0.5]
return [i * i for i in l2]
def f2(lst):
l1 = [i for i in lst if i < 0.5]
l2 = sorted(l1)
return [i * i for i in l2]
def f3(lst):
l1 = [i * i for i in lst]
l2 = sorted(l1)
return [i for i in l1 if i < (0.5 * 0.5)]
>>> f2 >> f1 > f3
Знайти трійку найуживаніших слів у тексті⚑
У файлі містяться слова, розділені пробілом. Наприклад: "abba com mother bill mother com abba dog abba mother com". Потрібно знайти і вивести трійку слів, які частіше всього зустрічаються разом (порядок не має значення). Тобто у моєму прикладі трійки слів це "abba com mother", "com mother bill", "mother bill mother" і так далі. Тут правильною відповіддю має бути "abba com mother" (частота — 3 рази).
from collections import defaultdict
text = "abba com mother bill mother com abba dog abba mother com"
words = text.split()
triplet_counts = defaultdict(int)
for i in range(len(words) - 2):
triplet = [words[i], words[i + 1], words[i + 2]]
triplet = tuple(sorted(triplet))
triplet_counts[triplet] += 1
most_common_triplet = max(triplet_counts, key=triplet_counts.get)
print("The most common triplet of words:", most_common_triplet) # ('abba', 'com', 'mother')
print("Frequency of occurrence:", triplet_counts[most_common_triplet]) # 3
Чи можна використати клас як ключ в словнику⚑
Що виведе print та чому?
class KeyHolder:
def __init__(self, key):
self.key = key
def __eq__(self, other):
return self.key == other.key
def __hash__(self):
return 0
d = {KeyHolder(1): 'a', KeyHolder(2): 'b'}
print(d) # {<__main__.KeyHolder object at 0x10>: 'a', <__main__.KeyHolder object at 0x20>: 'b'}
Функція hash буде повертати однакове значення в усіх випадках. Але виклик KeyHolder(2) не перезапише ключ, оскільки словник вміє працювати з колізіями. Коли ми отримуємо однакове значення хеша, далі проводиться порівняння за значенням. Оскільки в eq ми порівнюємо key, Python зрозуміє що це інший об'єкт, та помістить нову пару в хеш-таблицю. Проблемою цієї реалізації є те, що всі виклики hash будуть повертати однакове значення. Відповідно для того, щоб знайти по ключу відповідно значення, в негативному сценарії потрібно буде перебрати усі ключі. Тобто доступ буде O(n), а не O(1).
Дескриптор для валідації атрибутів⚑
from typing import Callable, Any
class Validation:
def __init__(self, validation_function: Callable[[Any], bool], error_msg: str) -> None:
self.validation_function = validation_function
self.error_msg = error_msg
def __call__(self, value):
if not self.validation_function(value):
raise ValueError(f"{value!r} {self.error_msg}")
class Field:
def __init__(self, *validations):
self._name = None
self.validations = validations
def __set_name__(self, owner, name):
self._name = name
def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__[self._name]
def validate(self, value):
for validation in self.validations:
validation(value)
def __set__(self, instance, value):
self.validate(value)
instance.__dict__[self._name] = value
class ClientClass:
descriptor = Field(
Validation(lambda x: isinstance(x, (int, float)), "is not a number"),
Validation(lambda x: x >= 0, "is not >= 0"),
)
>>> client = ClientClass()
>>> client.descriptor = 42
>>> client.descriptor
42
>>> client.descriptor = -42
Traceback (most recent call last):
...
ValueError: -42 is not >= 0
>>> client.descriptor = "invalid value"
Traceback (most recent call last):
...
ValueError: 'invalid value' is not a number
Дескриптор з метою запобігання видаленню атрибутів⚑
Дескриптор з метою запобігання видаленню атрибутів з об’єкта без необхідних адміністративних привілеїв.
class ProtectedAttribute:
def __init__(self, requires_role=None) -> None:
self.permission_required = requires_role
self._name = None
def __set_name__(self, owner, name):
self._name = name
def __set__(self, user, value):
if value is None:
raise ValueError(f"{self._name} can't be set to None")
user.__dict__[self._name] = value
def __delete__(self, user):
if self.permission_required in user.permissions:
user.__dict__[self._name] = None
else:
raise ValueError(
f"User {user!s} doesn't have {self.permission_required} permission"
)
class User:
"""Only users with 'admin' privileges can remove their email address."""
email = ProtectedAttribute(requires_role="admin")
def __init__(self, username: str, email: str, permission_list: list = None) -> None:
self.username = username
self.email = email
self.permissions = permission_list or []
def __str__(self):
return self.username
>>> admin = User("root", "root@d.com", ["admin"])
>>> user = User("user", "user1@d.com", ["email", "helpdesk"])
>>> admin.email
'root@d.com'
>>> del admin.email
>>> admin.email is None
True
>>> user.email
'user1@d.com'
>>> user.email = None
Traceback (most recent call last):
...
ValueError: email can't be set to None
>>> del user.email
Traceback (most recent call last):
...
ValueError: User user doesn't have admin permission
Клас User вимагає, щоб ім'я користувача та електронна пошта були обов'язковими параметрами. Згідно з його методом __init__, об'єкт не може бути користувачем, якщо у нього немає атрибута email. Якщо видалити цей атрибут і повністю вилучити його з об'єкта, буде некоректний об'єкт, який не відповідає інтерфейсу, визначеному класом User. Інший об'єкт, який буде взаємодіяти з цим користувачем, буде очікувати, що у нього буде атрибут email.
Тому "видалення" електронної пошти просто встановлює її значення на None. З тієї ж причини потрібно заборонити присвоєння значення None для цього атрибута, оскільки це обійде механізм, який встановлений в методі __delete__.
Максимальна кількість пересічних інтервалів⚑
Summary
Класична задача про кімнати/ресурси: знайти момент, де перетинається найбільше інтервалів. Оптимальне рішення - sweep line:
+1на початок інтервалу,-1на кінець, сортування за часом, префіксна сума з відстеженням максимуму. Складність O(N log N).
Дано список інтервалів [start, end] (бронювання, зустрічі, заселення). Потрібно визначити максимальну кількість інтервалів, що перетинаються в один момент часу - тобто скільки кімнат (ресурсів) потрібно одночасно. Та сама задача зустрічається у формулюваннях "meeting rooms", "планування слотів", "виділення ресурсів".
Sweep line (події заселення/виселення)
Кожен інтервал розкладають на дві події: +1 у момент start (заселення) і -1 у момент end (виселення). Події сортують за часом і проходять префіксною сумою, відстежуючи максимум. За рівного часу виселення має передувати заселенню - інтервал, що закінчується в момент t, не перетинається з тим, що починається в t. Кортеж (time, delta) дає це автоматично, бо -1 < 1.
def max_overlap(intervals: list[tuple[int, int]]) -> int:
events: list[tuple[int, int]] = []
for start, end in intervals:
events.append((start, 1)) # check-in
events.append((end, -1)) # check-out
events.sort() # by time; -1 before +1 on ties
current = best = 0
for _, delta in events:
current += delta
best = max(best, current)
return best
max_overlap([(1, 5), (2, 6), (3, 7), (8, 10)]) # 3
Складність - O(N log N) за рахунок сортування подій; це нижня межа для задачі. Додаткова пам'ять - O(N) на масив подій.
Перебір по шкалі часу. Альтернатива - пройти кожну цілу точку від мінімального до максимального часу й порахувати інтервали, що її покривають. Це O(N * D), де D - діапазон значень. Рішення прийнятне, лише коли значення обмежені (наприклад, години доби); при розріджених великих координатах (D у мільярди при кількох інтервалах) воно деградує, тоді як sweep line залежить лише від кількості інтервалів N.
Пошук слова в матриці (DFS + backtracking)⚑
Summary
Перевірити, чи можна скласти слово, рухаючись сусідніми клітинками сітки літер (вгору, вниз, ліворуч, праворуч), використовуючи кожну клітинку щонайбільше раз. Розв'язок - пошук у глибину з backtracking: клітинку позначають відвіданою на час рекурсії й відновлюють після виходу.
Дано матрицю літер і слово; знайти, чи існує шлях суміжними клітинками, що утворює це слово. Кожну клітинку можна використати лише раз у межах одного шляху. Класична задача (LeetCode "Word Search").
Запускають DFS з кожної клітинки, що збігається з першою літерою. На кожному кроці клітинку тимчасово замінюють маркером (або тримають множину відвіданих), щоб той самий шлях не повертався в неї, а після рекурсії - відновлюють (це і є backtracking).
def exist(board: list[list[str]], word: str) -> bool:
rows, cols = len(board), len(board[0])
def dfs(r: int, c: int, i: int) -> bool:
if i == len(word):
return True
if not (0 <= r < rows and 0 <= c < cols) or board[r][c] != word[i]:
return False
board[r][c] = "#" # mark visited (avoid revisiting in this path)
found = (
dfs(r + 1, c, i + 1) or dfs(r - 1, c, i + 1)
or dfs(r, c + 1, i + 1) or dfs(r, c - 1, i + 1)
)
board[r][c] = word[i] # restore for other paths (backtracking)
return found
return any(dfs(r, c, 0) for r in range(rows) for c in range(cols))
board = [["A", "B", "C", "E"],
["S", "F", "C", "S"],
["A", "D", "E", "E"]]
exist(board, "ABCCED") # True
exist(board, "ABCB") # False (B can't be reused)
Складність - O(M * N * 4^L) у гіршому випадку, де M*N - стартові клітинки, а 4^L - розгалуження пошуку на довжину слова L (фактично менше через відсікання за літерою й межами). Додаткова пам'ять - O(L) на глибину рекурсії. Ключовий момент, який часто пропускають, - відновлення клітинки після рекурсії; без нього одна гілка пошуку "з'їдає" клітинки в інших.
Максимум у кожному вікні (монотонний дек)⚑
Summary
Для кожного вікна розміру
kзнайти максимум. Наївно - O(n·k). Оптимально - O(n) через монотонний (спадний) дек, що зберігає індекси кандидатів: на початку дека завжди індекс поточного максимуму, а застарілі й менші кандидати відкидаються.
Дано масив nums і розмір вікна k; повернути максимум кожного вікна, що рухається зліва направо (LeetCode "Sliding Window Maximum"). Наївний перебір кожного вікна - O(n·k).
Монотонний дек
Тримають дек індексів, значення в яких спадають. Перед додаванням нового елемента з хвоста дека прибирають усі індекси з меншими або рівними значеннями - вони вже не зможуть стати максимумом, бо новий елемент і свіжіший, і більший. На початку дека лишається індекс максимуму поточного вікна. Коли індекс на початку виходить за ліву межу вікна, його прибирають. Зберігають саме індекси, а не значення, щоб визначати момент виходу елемента з вікна.
from collections import deque
def max_sliding_window(nums: list[int], k: int) -> list[int]:
dq: deque[int] = deque() # indices; their values stay decreasing
result: list[int] = []
for i, n in enumerate(nums):
while dq and nums[dq[-1]] <= n: # drop smaller/equal tail candidates
dq.pop()
dq.append(i)
if dq[0] == i - k: # front index slid out of the window
dq.popleft()
if i >= k - 1: # window full -> record its max
result.append(nums[dq[0]])
return result
max_sliding_window([1, 3, -1, -3, 5, 3, 6, 7], 3) # [3, 3, 5, 5, 6, 7]
Складність - O(n): кожен індекс додається в дек і прибирається з нього щонайбільше раз, тому сумарна робота лінійна, попри вкладений while. Додаткова пам'ять - O(k) на дек.
Перемістити нулі в кінець (два вказівники, in-place)⚑
Summary
Перенести всі нулі масиву в кінець, зберігши порядок ненульових елементів, за O(n) часу і O(1) додаткової пам'яті: вказівник
posтримає позицію наступного ненульового елемента, кожен ненульовий елемент переставляють наposі зсуваютьpos.
Дано масив; перенести всі 0 у кінець, не змінюючи відносний порядок інших елементів, на місці (LeetCode "Move Zeroes"). Наївне рішення з окремим списком коштує O(n) додаткової пам'яті; оптимальне обходиться O(1).
Два вказівники
Індекс pos указує, куди покласти наступний ненульовий елемент. Масив проходять одним циклом; щойно трапляється ненульове значення, його міняють місцями з елементом на pos і збільшують pos. Нулі внаслідок цього "спливають" у кінець.
def move_zeroes(nums: list[int]) -> None:
pos = 0 # next slot for a non-zero element
for i, n in enumerate(nums):
if n != 0:
nums[pos], nums[i] = nums[i], nums[pos]
pos += 1
a = [0, 1, 0, 3, 12]
move_zeroes(a)
a # [1, 3, 12, 0, 0]
Складність - O(n) часу (один прохід) і O(1) додаткової пам'яті. Інваріант: усе ліворуч від pos - ненульові елементи у вихідному порядку, між pos та i - лише нулі.
Декоратор обмеження частоти викликів (rate limit)⚑
Summary
Декоратор, що дозволяє не більше N викликів за ковзне вікно в 1 секунду, а зайві виклики тихо пропускає. Зберігають часові мітки викликів і відкидають ті, що старші за вікно.
Функцію викликають з багатьох місць, а зовнішній сервіс банить за надто часті звернення. Потрібен декоратор, що пропускає максимум N викликів за останню секунду (ковзне вікно), а решту ігнорує.
import time
from collections import deque
from functools import wraps
def rate_limit(max_calls: int, period: float = 1.0):
def decorator(func):
calls: deque[float] = deque()
@wraps(func)
def wrapper(*args, **kwargs):
now = time.monotonic() # monotonic clock: never jumps backward (NTP-safe)
while calls and now - calls[0] >= period:
calls.popleft() # drop timestamps outside the window
if len(calls) >= max_calls:
return None # limit reached -> skip this call
calls.append(now)
return func(*args, **kwargs)
return wrapper
return decorator
@rate_limit(3) # at most 3 calls per second
def hit_api(): ...
Ключові моменти: time.monotonic() (а не time.time(), який може стрибнути назад при NTP-корекції); одне зчитування часу на виклик; ковзне вікно (старі мітки прибирають), а не скидання лічильника на межі секунди.
Кодування довжин серій (run-length encoding)⚑
Summary
Стиснути рядок, замінивши серії однакових символів на символ + кількість:
"aaabb"→"a3b2". Один прохід зі станом (поточний символ + лічильник); серію дописують на межі та після циклу.
def rle(s: str) -> str:
if not s:
return ""
out, prev, count = [], s[0], 1
for ch in s[1:]:
if ch == prev:
count += 1
else:
out.append(prev + (str(count) if count > 1 else ""))
prev, count = ch, 1
out.append(prev + (str(count) if count > 1 else "")) # flush the final run
return "".join(out)
rle("aaabbc") # 'a3b2c'
Складність - O(n) часу. Ітерують по символах напряму (не за індексом) і тримають попередній символ; критично не забути дописати останню серію після циклу.
Перевірка анаграми⚑
Summary
Два рядки - анаграми, якщо складаються з тих самих символів у тій самій кількості (різниться лише порядок). Найпростіше - порівняти лічильники символів:
Counter(a) == Counter(b).
from collections import Counter
def is_anagram(a: str, b: str) -> bool:
return len(a) == len(b) and Counter(a) == Counter(b)
is_anagram("listen", "silent") # True
is_anagram("aab", "abb") # False - different counts of 'a'/'b'
Counter враховує і набір символів, і їхню кількість, тому випадок з однаковим набором, але різними частотами ("aab" vs "abb") коректно дає False. Складність - O(n); перевірка довжини на початку дає швидкий вихід для рядків різної довжини.
Two Sum (пара з заданою сумою)⚑
Summary
Знайти два елементи масиву, що дають у сумі
target. Наївно - O(n²) перебір усіх пар. Оптимально - O(n) через хеш-таблицю: для кожного числа перевіряють, чи трапилося раніше доповненняtarget - x.
Дано масив і число target; повернути індекси двох елементів, що дають у сумі target (LeetCode "Two Sum").
Хеш-таблиця за один прохід
Ідуть масивом, тримаючи словник значення -> індекс уже побачених. Для кожного x шукають доповнення target - x у словнику: якщо воно там, відповідь знайдено - O(n) часу, O(n) пам'яті.
def two_sum(nums: list[int], target: int) -> tuple[int, int] | None:
seen: dict[int, int] = {} # value -> index
for i, x in enumerate(nums):
if target - x in seen: # complement already seen?
return seen[target - x], i
seen[x] = i
return None
two_sum([2, 7, 11, 15], 9) # (0, 1)
Альтернатива для відсортованого масиву - два вказівники з обох кінців назустріч (звужують діапазон, порівнюючи суму з target): O(n) і без додаткової пам'яті, але потребує сортування O(n log n), якщо вхід не відсортований. Тому для довільного масиву хеш-таблиця краща; перед розв'язанням варто уточнити, чи відсортований вхід.