Почти всё мы решаем массивом и словарём — и обычно норм
Но есть места, где привычная структура на ровном месте начинает тормозить, и на проде это больно
Причина почти всегда одна — взяли не ту структуру под задачу
Собрал частые проблемы и их решения
1. Проверка «есть ли такой элемент» в списке 🐌 Классика:
if user_id in users_list: # бежит по всему списку
Каждая такая проверка перебирает весь список от начала до конца, пока не найдёт
Один раз — ерунда
А если это внутри цикла по другому списку — получаешь перебор в переборе, и на десятках тысяч элементов всё встаёт
Причём в дебаге ты этого не увидишь
Если тебе нужно только «есть или нет» — бери множество (set):
users = set(users_list)
if user_id in users: # мгновенно, по хэшу
Set устроен так, что находит элемент сразу, не перебирая
Заплатил один раз за построение множества — дальше все проверки почти ничего не стоят
2. Удаление из начала списка ✂️ Тоже коварное:
queue.pop(0) # весь список сдвигается влево
Удалил первый элемент — и все остальные физически сьехало на одну позицию
На маленьком списке незаметно, а на большом каждый такой pop проходит по всему массиву
Есть deque — очередь, из которой можно быстро брать и с начала, и с конца:
from collections import deque
q = deque()
q.append(x) # в конец
q.popleft() # с начала, без сдвигов
Внутри это не сплошной кусок памяти, а связанные блоки, поэтому выдернуть элемент с любого конца — мгновенно
3. Считаем, сколько раз что встретилось 🔢
Обычно это пишут руками:
counts = {}
for x in items:
if x in counts:
counts[x] += 1
else:
counts[x] = 1Знакомо?
А между тем для этого есть готовый инструмент:
from collections import Counter
counts = Counter(items)
counts.most_common(3) # ещё и топ-3 сразу отдаст
Одна строчка вместо цикла с проверками — и сразу без багов. Плюс
Counter умеет складываться и вычитаться, так что если считаешь по кускам, можно просто сложить результаты4. Нужно вытащить десять самых больших значений из миллиона 🗑
sorted(data)[-10:] # перелопатил всё ради десятки
Ты отсортировал целый массив, чтобы взять с хвоста десять штук — остальные 999 990 отсортировал впустую
Для этого есть куча (heap) — она держит в уме только нужные элементы, а не гоняет весь массив:
import heapq
heapq.nlargest(10, data)
Куче надо следить всего за десятью значениями, поэтому на больших данных она заметно быстрее полной сортировки.
То же и для «топ-N самых маленьких» —
nsmallest5. Группировка с вечной проверкой «а есть уже такой ключ?» 🏗
groups = {}
for user in users:
if user.city not in groups:
groups[user.city] = []
groups[user.city].append(user)defaultdict убирает всю эту возню:
from collections import defaultdict
groups = defaultdict(list)
for user in users:
groups[user.city].append(user) # пустой список создастся сам
Просишь ключ, которого нет — он сам подставит пустой список, и можно сразу пихать
Тот же приём с
defaultdict(int) для счётчиков, если Counter почему-то не подходитА как часто вы используете это или пишите по старинке? 🤔
#algorithms #python #performance #backend #dev #programming