Как построить рекомендации треков по истории прослушиваний
Есть только история взаимодействий пользователей с треками. Как построить первую рекомендательную систему?
Сначала проговорите ответ вслух или тезисами.
Формулы, план решения, риски и примеры.
Откройте разбор только после своей попытки.
Показать разбор
Короткий ответ
Начать с implicit collaborative filtering: user-item матрица по прослушиваниям, likes/skips, ALS/BPR/item2item. Добавить popular/recency fallback, фильтры уже прослушанного и позже реранжирование с контекстом сессии.
Подробный разбор
Если есть только история прослушиваний, первый baseline - implicit feedback. События нужно взвесить: полное прослушивание сильнее короткого skip, like сильнее обычного play, повторные прослушивания можно логарифмировать. Дальше подойдут ALS, BPR, item-to-item co-occurrence или простой nearest-neighbor по item embeddings.
Для выдачи нужна генерация кандидатов: похожие треки, популярное в сегменте, свежее, editorial/business rules. После этого можно делать реранжирование: не повторять только что прослушанное, добавить diversity по артистам/жанрам, учитывать устройство, время дня, контекст сессии и skip history.
Cold start решается fallback-ами и content features: жанр, артист, аудио embeddings, текст/метаданные. Даже если первая версия коллаборативная, архитектура должна позволять позже добавить content-based кандидатов.
Типичные ошибки
- Считать все прослушивания одинаково полезными.
- Не фильтровать повторы и уже прослушанные треки.
- Не иметь fallback для новых пользователей и новых треков.
Метрики музыкальных рекомендаций
Какие метрики смотреть для рекомендательной системы треков?
Сначала проговорите ответ вслух или тезисами.
Формулы, план решения, риски и примеры.
Откройте разбор только после своей попытки.
Показать разбор
Короткий ответ
Offline: Recall@K, NDCG@K, HitRate, coverage/diversity по артистам и жанрам. Online: play-through rate, skip rate, likes, saves, listening time, session length, retention и guardrails по повторам/усталости.
Подробный разбор
Offline можно предсказывать held-out listens/likes/skips: Recall@K и HitRate показывают, попал ли нужный трек в рекомендации, NDCG учитывает позицию, coverage и diversity показывают, не схлопнулась ли выдача в популярные треки. Для музыки важны срезы по новым трекам, новым пользователям, жанрам и артистам.
Online-метрики должны отражать пользовательский опыт: доля дослушиваний, skip rate в первые секунды, likes, saves, добавления в плейлист, listening time, session length, возвраты в продукт. Guardrails: повторяемость, слишком узкая выдача, доля explicit/неподходящего контента, latency.
Нельзя смотреть только short-term clicks/plays: модель может начать крутить знакомые хиты, поднять первые прослушивания и ухудшить discovery или долгосрочное удержание.
Типичные ошибки
- Оптимизировать только play count.
- Не измерять diversity и повторяемость.
- Не разделять метрики для новых и активных пользователей.
Изменяемые объекты и копирование списков в Python
Что произойдет, если присвоить один список другой переменной и изменить его? Чем отличаются shallow copy и deep copy?
Сначала проговорите ответ вслух или тезисами.
Формулы, план решения, риски и примеры.
Откройте разбор только после своей попытки.
Показать разбор
Короткий ответ
Присваивание не копирует список, а добавляет новую ссылку на тот же объект. Shallow copy копирует внешний контейнер, но вложенные объекты остаются общими. Deep copy рекурсивно копирует вложенные объекты.
Подробный разбор
В Python переменная хранит ссылку на объект. Если написать b = a, где a - список, то a и b указывают на один и тот же список. Поэтому b.append(1) изменит объект, который виден и через a.
Shallow copy можно сделать через a.copy(), list(a) или a[:]. Она создает новый внешний список, но элементы внутри остаются теми же объектами. Если внутри лежит вложенный список, изменение вложенного списка будет видно в обеих копиях.
Deep copy из модуля copy рекурсивно копирует вложенные объекты. Она нужна для сложных структур, но может быть дорогой и не всегда желательной, если часть объектов должна оставаться общей.
Типичные ошибки
- Думать, что `b = a` создает новый список.
- Использовать shallow copy для вложенных изменяемых структур.
- Делать deepcopy без необходимости на больших объектах.
Декораторы и генераторы в Python
Что такое декоратор, зачем он нужен, и почему код внутри генератора выполняется не при создании, а при итерации?
Сначала проговорите ответ вслух или тезисами.
Формулы, план решения, риски и примеры.
Откройте разбор только после своей попытки.
Показать разбор
Короткий ответ
Декоратор принимает функцию и возвращает новую функцию-обертку. Генераторная функция при вызове возвращает generator object; ее тело начинает выполняться только при next(), цикле или list(generator).
Подробный разбор
Декоратор - это обычная функция высшего порядка. Синтаксис @decorator примерно равен func = decorator(func). Обертка может логировать вызовы, проверять права, кешировать результат, менять аргументы или выполнять код до/после исходной функции. Для сохранения имени и docstring обычно используют functools.wraps.
Генераторная функция содержит yield. При вызове она не выполняет тело сразу, а возвращает generator object. Код внутри начнет выполняться, когда кто-то запросит следующий элемент: next(g), for, list(g). После каждого yield состояние функции сохраняется до следующего шага.
Поэтому принты до/после yield появляются в момент итерации, а не в момент создания генератора. Это часто спрашивают, чтобы проверить понимание lazy evaluation.
Типичные ошибки
- Думать, что генератор выполняется при вызове функции.
- Писать декоратор без `*args, **kwargs` и ломать сигнатуру вызова.
- Забывать `functools.wraps` в production-декораторах.
Как ревьюить класс для чтения и обработки данных
На собеседовании показывают класс, который читает файл, хранит DataFrame и делает обработку. Какие проблемы искать в таком коде?
Сначала проговорите ответ вслух или тезисами.
Формулы, план решения, риски и примеры.
Откройте разбор только после своей попытки.
Показать разбор
Короткий ответ
Смотреть состояние объекта, мутабельность, понятный lifecycle обработки, ошибки I/O, memory usage, idempotency, валидацию входа и то, не отдается ли наружу внутренняя изменяемая структура без копии.
Подробный разбор
В таком ревью сначала нужно понять lifecycle: когда файл читается, когда данные считаются обработанными, где меняется флаг состояния и можно ли вызвать методы в неправильном порядке. Если property возвращает внутренний DataFrame, внешний код может случайно изменить состояние объекта; иногда нужна копия или read-only контракт.
Дальше проверяются I/O и ошибки: существует ли путь, корректен ли encoding/separator, что происходит с пустым файлом, битым CSV, большим файлом, частичной записью. Если данные большие, чтение всего файла в память может быть проблемой; нужны chunks, streaming или явный лимит.
Еще важны имена методов и ответственность. Метод check не должен внезапно выполнять тяжелую обработку, если его название обещает только проверку. Ошибки лучше делать явными исключениями с понятным сообщением, а не неявным падением в середине пайплайна.
Типичные ошибки
- Комментировать только стиль, не разобрав lifecycle объекта.
- Не заметить, что наружу отдается изменяемый внутренний DataFrame.
- Не спросить, что будет на большом или битом файле.
Минимальное число лодок для перевозки людей
Дан массив people, где people[i] - вес человека, и число limit - максимальный вес лодки.
Каждая лодка может перевезти не больше двух человек. Гарантируется, что вес каждого человека не больше limit.
Напишите функцию num_rescue_boats(people, limit), которая вернет минимальное число лодок.
Решение прямо на странице
Напишите код, запустите проверки и только потом открывайте разбор.
Нажмите «Запустить проверки» или Ctrl+Enter.
Показать разбор
Идея решения
Сортируем веса. Самого тяжелого оставшегося человека все равно нужно посадить в очередную лодку. Если самый легкий оставшийся помещается с ним, выгодно посадить их вместе: более тяжелый партнер точно не подойдет лучше. Если не помещается, самый тяжелый едет один. Так каждый шаг оптимально уменьшает задачу.
Эталонный код
def num_rescue_boats(people, limit):
people.sort()
left = 0
right = len(people) - 1
boats = 0
while left <= right:
if people[left] + people[right] <= limit:
left += 1
right -= 1
boats += 1
return boats