Кодинг на Python
Алгоритмические задачи и обработка данных, которые аналитик решает на чистом Python.
18 вопросов
MiddleКодОчень частоСуммировать выручку по дням из лог-файла, не влезающего в память целиком
Суммировать выручку по дням из лог-файла, не влезающего в память целиком
Итерируйте файловый объект построчно — Python читает лениво, память остаётся плоской. Разберите день и сумму строки, прибавляя сумму в dict с ключом-днём. Не вызывайте read() или readlines() — они грузят весь файл сразу.
Типичные ошибки
- ✗Вызывать read() или readlines(), загружая весь файл
- ✗Резать по границам байт и рвать строку пополам
- ✗Хвататься за pandas на файле больше памяти
Уточняющие вопросы
- →Почему итерация файлового объекта держит память плоской?
- →Как обработать битую строку, не упав?
MiddleКодОчень частоВернуть первое и последнее событие каждого пользователя за один проход
Вернуть первое и последнее событие каждого пользователя за один проход
Держите dict по пользователю. Для каждого события, если пользователь новый, кладите (ts, ts), иначе расширяйте пару до (min(first, ts), max(last, ts)). Один проход O(n) даёт раннее и позднее время каждого пользователя — без сортировки и пересканирования.
Типичные ошибки
- ✗Сортировать весь список, когда хватает одного прохода min/max
- ✗Фильтровать на пользователя, пересканируя список O(n*u) раз
- ✗Считать, что порядок входа совпадает с порядком времён
Уточняющие вопросы
- →Как заодно вернуть число событий каждого пользователя?
- →Что меняется, если у двух событий одно время?
MiddleКодОчень частоРазбить отсортированный поток событий на сессии по 30-минутному разрыву
Разбить отсортированный поток событий на сессии по 30-минутному разрыву
Пройдите по меткам один раз, храня начало сессии и предыдущее событие. Когда шаг до следующего события превышает разрыв, закройте сессию — последняя минус первая — и начните новую. После цикла сбросьте последнюю; это один проход O(n).
Типичные ошибки
- ✗Мерить разрыв от начала сессии, а не от предыдущего события
- ✗Бить на фиксированные окна часов вместо разрывов между событиями
- ✗Забывать сбросить последнюю сессию после цикла
Уточняющие вопросы
- →Как вернуть число событий на сессию вместо длительностей?
- →Что меняется, если сортировка входа не гарантирована?
JuniorКодЧастоПосчитать, сколько id пользователей есть в обоих списках, за линейное время
Посчитать, сколько id пользователей есть в обоих списках, за линейное время
Превратите один список в set и посчитайте различные id другого в нём — членство в set это O(1), поэтому задача O(n + m). len(set(list_a) & set(list_b)) делает это одной строкой. Ловушка — if x in list_b, пересканирующий список.
Типичные ошибки
- ✗Использовать
if x in list_bв цикле, получая O(n*m) - ✗Забывать убрать дубли id внутри каждого списка
- ✗Сортировать, когда пересечение set проще и быстрее
Уточняющие вопросы
- →Почему перевод в set меняет класс сложности?
- →Как заодно вернуть сами id, а не только счёт?
JuniorКодЧастоПосчитать общую конверсию по сегментам очень разного размера
Посчитать общую конверсию по сегментам очень разного размера
Общая конверсия — сумма конверсий делить на сумму визитов, то есть среднее, взвешенное по размеру, а не среднее посегментных ставок. Усреднение даёт сегментам равный вес, и крошечный сегмент искажает итог.
Открыть задачу →Типичные ошибки
- ✗Усреднять посегментные ставки, будто сегменты одного размера
- ✗Взвешивать не по числу визитов, а по чему-то ещё
- ✗Забывать, что малый сегмент может качнуть простое среднее
Уточняющие вопросы
- →Когда простое среднее совпадёт со взвешенным?
- →Как взвесить по выручке вместо визитов?
MiddleКодЧастоПосчитать ARPU тестовой группы и неявного контроля
Посчитать ARPU тестовой группы и неявного контроля
Делят пользователей на тест (кто в test_users) и контроль (все остальные в gmv_by_user), затем делят суммарный GMV каждой группы на число её пользователей. Ловушка — знаменатель: пользователь с нулевым GMV всё равно считается пользователем, поэтому делят на число пользователей, а не платящих.
Типичные ошибки
- ✗Исключать пользователей с нулевым GMV из знаменателя
- ✗Отчитываться одним общим ARPU для обеих групп
- ✗Забывать, что контроль — все, кого нет в тест-множестве
Уточняющие вопросы
- →Почему пользователь с нулевым GMV должен остаться в знаменателе?
- →Как расширить это до ARPPU (только платящие)?
MiddleКодЧастоБутстрап 95% доверительного интервала для медианы суммы заказа
Бутстрап 95% доверительного интервала для медианы суммы заказа
Ресемплируйте значения с возвращением до размера n, берите медиану каждого ресемпла и повторяйте несколько тысяч раз. 2.5-й и 97.5-й перцентили этих бутстрап-медиан — это 95% интервал, без замкнутой формулы стандартной ошибки.
Типичные ошибки
- ✗Ресемплировать без возвращения или не того размера
- ✗Применять формулу нормального приближения к медиане
- ✗Брать перцентили сырых значений вместо бутстрап-медиан
Уточняющие вопросы
- →Почему ресемплировать ровно до n, а не до меньшего размера?
- →Как изменится интервал, если удвоить n_boot?
MiddleКодЧастоПосчитать потоковые среднее и дисперсию, не помещающиеся в память
Посчитать потоковые среднее и дисперсию, не помещающиеся в память
Уэлфорд держит счётчик, среднее и сумму квадратов отклонений M2, обновляя их на каждом элементе за один проход с памятью O(1). Выборочная дисперсия — M2 / (n - 1). Он избегает наивной sum(x**2) - sum(x)**2 / n, что катастрофически сокращается.
Типичные ошибки
- ✗Брать сумму квадратов минус квадрат суммы — катастрофическое вычитание
- ✗Буферизовать весь поток, срывая цель памяти O(1)
- ✗Делить на n вместо n-1 для выборочной дисперсии
Уточняющие вопросы
- →Почему наивная формула двух сумм теряет точность?
- →Как слить два независимо посчитанных частичных состояния?
SeniorКодЧастоСлить перекрывающиеся интервалы подписки каждого пользователя и посчитать покрытые дни
Слить перекрывающиеся интервалы подписки каждого пользователя и посчитать покрытые дни
Сгруппируйте по пользователю, отсортируйте по началу и пройдите один раз: держите текущий интервал, расширяйте конец при перекрытии, иначе закройте его (прибавив end - start + 1 дней) и откройте новый. Сумма закрытых длин считает каждый день раз.
Типичные ошибки
- ✗Суммировать длины интервалов, дважды считая перекрытия
- ✗Сливать интервалы между пользователями, а не внутри каждого
- ✗Проходить без сортировки интервалов каждого пользователя
Уточняющие вопросы
- →Как полуоткрытые интервалы
[start, end)изменят арифметику? - →Какова временная сложность на пользователя?
JuniorКодИногдаПосчитать сотрудников, отработавших не меньше нормы часов
Посчитать сотрудников, отработавших не меньше нормы часов
Считают элементы, проходящие порог: sum(1 for h in hours if h >= target), или sum(h >= target for h in hours), ведь булевы считаются как 0/1. Сравнение должно быть >= — точное достижение нормы засчитывается.
Типичные ошибки
- ✗Использовать > вместо >= и терять точные совпадения
- ✗Предполагать, что список отсортирован
- ✗Делить суммарные часы на норму вместо подсчёта
Уточняющие вопросы
- →Почему
>=важно для точного достижения нормы? - →Как заодно вернуть, кто выполнил, а не только сколько?
JuniorКодИногдаВернуть первый локальный минимум в списке
Вернуть первый локальный минимум в списке
Сканируют внутренние индексы 1..len-2 и возвращают x[i] на первом i, где x[i] < x[i-1] and x[i] < x[i+1]; возвращают None, если совпадений нет. Решает обработка границ: при только внутренних кандидатах списки короче 3 не имеют локального минимума.
Типичные ошибки
- ✗Путать глобальный минимум с первым локальным
- ✗Проверять только одного соседа вместо обоих
- ✗Неверно считать первый/последний элемент кандидатом
Уточняющие вопросы
- →Как бы вы обработали границы, будь они допустимы?
- →Какова временная сложность вашего прохода?
JuniorТеорияИногдаПочему x in a_list внутри цикла — классическая ошибка аналитика по скорости?
Почему x in a_list внутри цикла — классическая ошибка аналитика по скорости?
x in a_list — это O(n): список сканируется до x, поэтому в цикле по m элементам выходит O(n * m). x in a_set или x in a_dict в среднем O(1) — ключ хешируется в корзину, и set делает членство константным.
Типичные ошибки
- ✗Считать, что
inна списке — за константу или логарифм - ✗Думать, что цена в накладных расходах цикла, а не в O(n)-скане
- ✗Оставлять коллекцию для поиска списком внутри горячего цикла
Уточняющие вопросы
- →Что ломается, если искомые id нехешируемы?
- →Сколько памяти стоит перевод в set?
MiddleКодИногдаМаксимальная сумма несоседних домов
Максимальная сумма несоседних домов
Динамическое программирование с двумя скользящими значениями. Для каждого дома лучший итог до него — max(пропустить = лучшее_без_предыдущего, взять = лучшее_до_предыдущего + nums[i]). Хранят два скользящих значения и обновляют их на каждом доме; ответ — финальный текущий максимум. O(n) времени, O(1) памяти.
Типичные ошибки
- ✗Считать дома с чётным индексом всегда оптимальными
- ✗Жадно брать наибольшие значения без учёта структуры
- ✗Забывать выбор «взять или пропустить» на каждом доме
Уточняющие вопросы
- →Как меняется рекуррента, если дома образуют круг?
- →Почему жадный подход «крупнейшее первым» здесь неверен?
MiddleКодИногдаНаибольшее произведение двух чисел в списке
Наибольшее произведение двух чисел в списке
Ответ — max(top1 * top2, bottom1 * bottom2) — два наибольших ИЛИ два наименьших. Второй кандидат важен, потому что два больших по модулю отрицательных дают большой положительный результат. Находят два наибольших и два наименьших за один проход O(n) (или sorted за O(n log n)).
Типичные ошибки
- ✗Перемножать только два наибольших, упуская два отрицательных
- ✗Соединять максимум с минимумом вместо двух краёв
- ✗Терять знаки, беря модули
Уточняющие вопросы
- →На каком входе подход «только два наибольших» проваливается?
- →Как сделать это за один проход без сортировки?
MiddleПроизводительностьИногдаПочему подсчёт через list.count на каждое значение намного медленнее одного прохода Counter?
Почему подсчёт через list.count на каждое значение намного медленнее одного прохода Counter?
data.count(v) — это скан O(n); запуск на каждое различное значение делает подсчёт O(n·u), почти O(n²). Counter(data) (или обычный dict) хеширует каждый элемент один раз за проход O(n), поэтому подсчёт падает с 40с до долей секунды.
Типичные ошибки
- ✗Винить накладные расходы цикла, а не O(n)-пересканинг
- ✗Звать
list.countилиinна каждое уникальное значение - ✗Считать, что Counter выигрывает константу, а не класс сложности
Уточняющие вопросы
- →Когда
list.countвнутри цикла всё же приемлем? - →Как
Counterведёт себя на нехешируемых элементах?
JuniorКодРедкоПроверить, делится ли одно из двух положительных чисел на другое нацело
Проверить, делится ли одно из двух положительных чисел на другое нацело
Проверяют обе стороны остатка: возвращают 1 if (b % a == 0 or a % b == 0) else 0. Нулевой остаток с любой стороны значит, что одно число делит другое. Для a=15, b=6 ни 15 % 6, ни 6 % 15 не ноль → 0.
Типичные ошибки
- ✗Проверять только одно направление деления
- ✗Путать делимость с чётностью суммы
- ✗Считать, что делятся нацело только равные числа
Уточняющие вопросы
- →Почему для положительных входов защита от деления на ноль не нужна?
- →Как изменился бы ответ, если бы допускался ноль?
MiddleКодРедкоСгруппировать список строк по анаграммам
Сгруппировать список строк по анаграммам
Раскладывают строки в словарь по канонической сигнатуре, общей для всех анаграмм. Подходит кортеж отсортированных символов tuple(sorted(s)) (или кортеж из 26 счётчиков букв — O(n) на строку вместо O(n log n)).
Типичные ошибки
- ✗Группировать по длине, смешивая не-анаграммы
- ✗Ключевать по первой букве или сумме ASCII — даёт коллизии
- ✗Использовать O(n^2) попарное сравнение вместо ключа
Уточняющие вопросы
- →Почему ключ-счётчик букв быстрее сортировки на строку?
- →Как ведёт себя случай пустой строки с вашим ключом?
MiddleКодРедкоПосчитать «честно чётные» числа от 1 до n
Посчитать «честно чётные» числа от 1 до n
Считают числа, у которых каждая цифра в {0,2,4,6,8}. Прямой проход: sum(1 for k in range(1, n+1) if all(d in '02468' for d in str(k))). Ловушка — прочитать «чётное число» как value % 2 == 0 — здесь проверка по цифрам, а не по чётности числа.
Типичные ошибки
- ✗Считать обычные чётные (value % 2) вместо всех-цифр-чётных
- ✗Проверять только последнюю цифру
- ✗Использовать чётность суммы цифр вместо чётности каждой цифры
Уточняющие вопросы
- →Почему 10 не проходит тест на честную чётность?
- →Как digit-DP подход масштабирует это для огромных n?