✅ Решения заданий

⚡ Решения в двух словах

  • 1 — {v: k for k, v in d.items()}.
  • 2 — {k: mapping.get(v, v) for k, v in d.items()}.
  • 3 — guard clause для отрицательных n, базовый случай n <= 1.
  • 4 — делим диапазон пополам и ищем в нужной половине.
  • 5 — n % 10 + sum_digits(n // 10).
  • 6 — рекурсивно обходим список и суммируем числа.
  • 7 — для каждого типа коллекции создаём новую копию рекурсивно.
  • 8 — " | ".join(str(item) for item in data).
⚠️ Рекомендация: попробуйте решить задания самостоятельно, прежде чем смотреть ответы.

Решение задания 1. Инверсия словаря

# solution_01_invert_dict.py
def invert_dict(original: dict[str, int]) -> dict[int, str]:
    """Возвращает словарь с инвертированными ключами и значениями."""
    return {value: key for key, value in original.items()}


original_dict = {"a": 1, "b": 2, "c": 3}
print("Инверсированный словарь:", invert_dict(original_dict))

Логика: по каждой паре ключ-значение создаём новую пару значение-ключ.

Решение задания 2. Из чисел в слова

# solution_02_numbers_to_words.py
def translate_values(
    data: dict[str, int],
    mapping: dict[int, str]
) -> dict[str, str]:
    """Заменяет числовые значения словаря на строки по mapping."""
    return {
        key: mapping.get(value, str(value))
        for key, value in data.items()
    }


number_to_word = {1: "один", 2: "два", 3: "три"}
data = {"x": 1, "y": 2, "z": 3}
print(translate_values(data, number_to_word))

Логика: для каждого значения ищем соответствие в словаре перевода. Если его нет — оставляем строковое представление исходного значения.

Решение задания 3. Факториал с проверкой

# solution_03_factorial.py
def factorial(n: int) -> int:
    """Возвращает факториал неотрицательного целого числа."""
    if n < 0:
        raise ValueError("n must be non-negative")
    if n == 0 or n == 1:
        return 1
    return n * factorial(n - 1)


print(factorial(5))   # 120
print(factorial(0))   # 1
# factorial(-1)  # ValueError

Логика: guard clause отсекает отрицательные числа, базовый случай обрабатывает 0 и 1, рекурсивный случай сводит задачу к меньшему числу.

Решение задания 4. Бинарный поиск

# solution_04_binary_search.py
from typing import Optional


def binary_search(
    arr: list[int],
    target: int,
    left: int,
    right: int
) -> Optional[int]:
    """Ищет target в отсортированном списке arr. Возвращает индекс или None."""
    if left > right:
        return None
    mid = (left + right) // 2
    if arr[mid] == target:
        return mid
    if arr[mid] < target:
        return binary_search(arr, target, mid + 1, right)
    return binary_search(arr, target, left, mid - 1)


array = [1, 3, 5, 7, 9, 11, 13]
print(binary_search(array, 5, 0, len(array) - 1))   # 2
print(binary_search(array, 13, 0, len(array) - 1))  # 6
print(binary_search(array, 8, 0, len(array) - 1))   # None

Логика: базовый случай — пустой диапазон. Иначе сравниваем target с серединой и рекурсивно ищем в нужной половине.

Решение задания 5. Сумма цифр числа

# solution_05_sum_digits.py
def sum_digits(n: int) -> int:
    """Возвращает сумму цифр неотрицательного целого числа."""
    n = abs(n)
    if n == 0:
        return 0
    return n % 10 + sum_digits(n // 10)


num = 43197
print(sum_digits(num))  # 24

Логика: на каждом шаге отрезаем последнюю цифру и прибавляем её к сумме цифр оставшегося числа.

Решение задания 6. Сумма вложенных чисел

# solution_06_sum_nested.py
from typing import Iterable


def sum_nested(numbers) -> int:
    """Суммирует все целые числа во вложенных списках."""
    if isinstance(numbers, int):
        return numbers
    if isinstance(numbers, Iterable) and not isinstance(numbers, (str, bytes)):
        return sum(sum_nested(item) for item in numbers)
    return 0


nested_numbers = [1, [2, 3], [4, [5, 6]], 7]
print(sum_nested(nested_numbers))  # 28

Логика: если элемент — целое число, возвращаем его. Если — итерируемая коллекция (но не строка), рекурсивно суммируем элементы.

Решение задания 7. Собственный аналог deepcopy

# solution_07_deep_copy.py
def deep_copy(data):
    """Рекурсивно копирует списки, кортежи, множества и словари."""
    if isinstance(data, list):
        return [deep_copy(item) for item in data]
    if isinstance(data, dict):
        return {key: deep_copy(value) for key, value in data.items()}
    if isinstance(data, set):
        return {deep_copy(item) for item in data}
    if isinstance(data, tuple):
        return tuple(deep_copy(item) for item in data)
    return data


original_data = [
    [1, 2, 3],
    (4, [5, 6], {7, 8}),
    {"a": 9, "b": [10, 11]},
    "Hello",
    [12, (13, 14)],
    15.5,
    5
]
copied_data = deep_copy(original_data)
original_data[1][1][0] = 0

print(f"Исходный: {original_data}")
print(f"Копия:    {copied_data}")

Логика: для каждого изменяемого контейнера создаём новый объект и рекурсивно копируем содержимое. Неизменяемые скаляры возвращаем как есть.

Решение задания 8. Объединение данных в строку

# solution_08_join_data.py
from typing import Any


def join_data(data: list[Any]) -> str:
    """Возвращает строковое представление элементов списка, разделённых ' | '."""
    return " | ".join(str(item) for item in data)


data = [42, "hello", [1, 2, 3], {"a": 1, "b": 2}]
print(join_data(data))

Логика: преобразуем каждый элемент в строку и объединяем через " | ".