← Назад к вопросам

Какой алгоритм действий при добавлении элемента в словарь в Python?

2.0 Middle🔥 201 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Алгоритм добавления элемента в словарь (dict) в Python

Словари в Python реализованы на основе хеш-таблиц. Процесс добавления элемента состоит из нескольких шагов с использованием хеширования.

Структура хеш-таблицы

Внутри словарь хранит:

  1. Массив ячеек (hash table) фиксированного размера
  2. Хеш-функция для преобразования ключа в индекс
  3. Разрешение коллизий для обработки совпадений
┌─────────────────────────────────────────┐
│  Словарь {'a': 1, 'b': 2, 'c': 3}     │
├─────────────────────────────────────────┤
│ [0] → None                             │
│ [1] → [ключ='a', значение=1]          │
│ [2] → [ключ='b', значение=2]          │
│ [3] → None                             │
│ [4] → [ключ='c', значение=3]          │
│ [5] → None                             │
│ [6] → None                             │
│ [7] → None                             │
└─────────────────────────────────────────┘

Шаг за шагом: добавление элемента

Операция:

d = {}
d['hello'] = 42

Шаг 1: Вычисление хеша ключа

hash_value = hash('hello')
# Результат: целое число (может быть очень большим)
# Например: hash('hello') = -9223372036854775789

Шаг 2: Преобразование хеша в индекс

# Размер таблицы: 8
size = 8
index = hash_value % size
index = (-9223372036854775789) % 8 = 3

Шаг 3: Проверка ячейки на коллизию

# Если ячейка [3] пуста → вставляем
# Если там уже есть элемент с тем же ключом → обновляем
# Если там элемент с другим ключом → коллизия (решаем ниже)

Шаг 4: Вставка в ячейку

table[3] = ('hello', 42)  # (ключ, значение)

Полный пример процесса

# Начальный словарь пуст
d = {}
# Внутренний размер: 8 ячеек

# Шаг 1: Добавляем d['apple'] = 100
key = 'apple'
hash_val = hash('apple')  # -6848352157346573405
index = hash_val % 8      # 3
# table[3] = ('apple', 100)

# Шаг 2: Добавляем d['banana'] = 200
hash_val = hash('banana')  # 2783859921076103131
index = hash_val % 8       # 3 (КОЛЛИЗИЯ! Тот же индекс)
# Нужно разрешить коллизию (см. ниже)

# Шаг 3: Добавляем d['cherry'] = 300
hash_val = hash('cherry')  # 8765432109876543210
index = hash_val % 8       # 2 (свободная ячейка)
# table[2] = ('cherry', 300)

Разрешение коллизий

Получается, что разные ключи могут иметь одинаковый индекс. Python использует открытую адресацию (linear probing):

Алгоритм:

  1. Вычислили индекс, он занят
  2. Проверяем следующую ячейку (index + 1)
  3. Если свободна — вставляем
  4. Если занята — ищем дальше
  5. Повторяем, пока не найдем свободную ячейку
def add_to_dict(table, key, value):
    index = hash(key) % len(table)
    
    # Линейное зондирование (linear probing)
    while table[index] is not None:
        existing_key, _ = table[index]
        if existing_key == key:
            # Ключ уже существует - обновляем значение
            table[index] = (key, value)
            return
        # Коллизия - ищем следующую свободную ячейку
        index = (index + 1) % len(table)
    
    # Нашли свободную ячейку
    table[index] = (key, value)

Визуально:

Добавляем 'banana' (коллизия на индекс 3):

Шаг 1: индекс 3 занят ('apple')
Шаг 2: индекс 4 свободен → вставляем 'banana'

┌────┬─────────┬─────────┬─────────┬─────────┐
│ 0  │ 1       │ 2       │ 3       │ 4       │
├────┼─────────┼─────────┼─────────┼─────────┤
│ ∅  │ ∅       │ cherry  │ apple   │ banana  │
└────┴─────────┴─────────┴─────────┴─────────┘

Перехеширование (Resizing)

Когда словарь становится слишком полным (коэффициент нагрузки > 2/3), Python перестраивает таблицу:

# Изначально размер 8
d = {}
for i in range(8):
    d[f'key{i}'] = i
# Размер примерно 66% заполнен

# При добавлении 6-го элемента:
d['key5'] = 5
# Python создает НОВУЮ таблицу размером 16 (увеличивает вдвое)
# Все элементы перехешируются с новым размером:
# новый индекс = hash(key) % 16

Процесс перехеширования:

def resize_table(old_table, new_size):
    new_table = [None] * new_size
    
    # Перехешируем все элементы
    for item in old_table:
        if item is not None:
            key, value = item
            # Вычисляем новый индекс
            new_index = hash(key) % new_size
            new_table[new_index] = item
    
    return new_table

# Использование
old_size = 8
new_size = 16
new_table = resize_table(table, new_size)

Полный процесс в коде Python

# Симуляция добавления элемента в dict

class SimpleDict:
    def __init__(self, capacity=8):
        self.capacity = capacity
        self.size = 0
        self.table = [None] * capacity
    
    def __setitem__(self, key, value):
        # Шаг 1: Проверяем нужно ли перехешировать
        if self.size / self.capacity > 0.67:
            self._resize()
        
        # Шаг 2: Вычисляем индекс
        index = hash(key) % self.capacity
        
        # Шаг 3: Разрешаем коллизии (линейное зондирование)
        while self.table[index] is not None:
            existing_key, _ = self.table[index]
            if existing_key == key:
                # Обновляем существующий ключ
                self.table[index] = (key, value)
                return
            index = (index + 1) % self.capacity
        
        # Шаг 4: Вставляем новый элемент
        self.table[index] = (key, value)
        self.size += 1
    
    def __getitem__(self, key):
        # Получение элемента (аналогичный процесс)
        index = hash(key) % self.capacity
        
        while self.table[index] is not None:
            existing_key, value = self.table[index]
            if existing_key == key:
                return value
            index = (index + 1) % self.capacity
        
        raise KeyError(key)
    
    def _resize(self):
        # Перестраиваем таблицу
        old_table = self.table
        old_capacity = self.capacity
        
        self.capacity *= 2
        self.table = [None] * self.capacity
        self.size = 0
        
        # Перехешируем все элементы
        for item in old_table:
            if item is not None:
                key, value = item
                self[key] = value

# Использование
d = SimpleDict()
d['apple'] = 100
d['banana'] = 200
d['cherry'] = 300

print(d['apple'])  # 100
print(d['banana']) # 200

Сложность операций

ОперацияСредний случайХудший случай
ВставкаO(1)O(n) - при плохой хеш-функции
ПолучениеO(1)O(n) - при плохой хеш-функции
УдалениеO(1)O(n) - при плохой хеш-функции

О(1) в среднем — потому что коллизии редки при хорошей хеш-функции.

Требования к хешируемости

Объект должен быть хешируем:

  • Строки, числа, кортежи → хешируемы
  • Списки, словари → НЕ хешируемы (ошибка)
# ✅ Работает
d = {'key': 'value'}
d[(1, 2, 3)] = 'tuple'  # Кортежи хешируемы

# ❌ Ошибка
d[[1, 2, 3]] = 'list'  # TypeError: unhashable type: 'list'

# ❌ Ошибка
d[{'a': 1}] = 'dict'  # TypeError: unhashable type: 'dict'

Почему требуется хешируемость? Потому что нельзя перестраивать индекс каждый раз, когда ключ меняется. Хешируемость гарантирует, что hash(key) всегда вернет одно и то же значение.

Оптимизация: кеш хешей

Python кеширует хеши для строк и чисел:

# Первый раз
hash('hello')  # Вычисляется и кешируется

# Второй раз
hash('hello')  # Берется из кеша (намного быстрее)

Вывод

Алгоритм добавления в dict:

  1. hash() — вычислить хеш ключа
  2. % size — преобразовать в индекс (0..size-1)
  3. Проверить ячейку — может ли она принять элемент?
  4. Разрешить коллизию — если нужно (линейное зондирование)
  5. Вставить — поместить (ключ, значение) в ячейку
  6. Перехешировать — если словарь переполнен

Это объясняет, почему dict работает так быстро: O(1) в среднем, благодаря хеш-таблице и хорошей хеш-функции.