avp26/u7/hash_table.py
2026-10-30 00:25:46 +01:00

173 lines
8.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

class HashTable:
def __init__(self, capacity=10):
"""Vytvor prázdnu tabuľku s kladnou celočíselnou kapacitou."""
# Проверяем тип capacity он не должен быть bool, должен быть int
if type(capacity) is not int:
raise TypeError("Kapacita musi byt cele cislo.")
# capacity должна быть > 0
if capacity <= 0:
raise ValueError("Kapacita musi byt kladne cislo.")
self._capacity = capacity
self.empty = object() # Создаем обьект для пустых ячеек
self.deleted = object() # Создаем обьект для удаленных ячеек
self.slots = [self.empty] * capacity # создаем пустые ячейки в виде списка
self.size = 0 # Счетчик активных элементов
@property
def capacity(self):
"""Vráť aktuálny počet pozícií v poli."""
return self._capacity
def valid_problem(self, key):
# Ключи обязаны быть строками (str) если передан другой тип то выбрасываем исключение
if not isinstance(key, str):
raise TypeError("Kluc musi byt retazec")
def hash(self, key):
"""Vráť polynomiálny hash kľúča modulo aktuálna kapacita."""
# Проверяем тип ключа
self.valid_problem(key)
result = 0
for char in key: # считаем хеш по формуле с сайта
result = result * 31 + ord(char)
# делам чтобы индекс строго входил в диапазон [0, capacity - 1]
return result % self._capacity
def probe(self, key):
"""Vráť index kľúča alebo miesta na vloženie bez zmeny tabuľky."""
# Проверяем тип ключа
self.valid_problem(key)
# Получаем начальный хэш-индекс для данного ключа
start_idx = self.hash(key)
first_deleted_idx = None
for step in range(self._capacity):
# Вычисляем текущий индекс со сдвигом step и цикличным переходом через край массива (% capacity)
idx = (start_idx + step) % self._capacity
slot = self.slots[idx]
if slot is self.empty:
# Если ранее встретили удаленную ячейку возвращаем ее индекс
if first_deleted_idx is not None:
return first_deleted_idx
# Иначе возвращаем индекс этой первой чистой ячейки
return idx
if slot is self.deleted:
# Запоминаем индекс первой такой ячейки если еще не запомнили ранее
if first_deleted_idx is None:
first_deleted_idx = idx
# Изолированно продолжаем цикл дальше так как ключ может быть в другой ячейке
# Активная пара (key, value)
elif slot[0] == key:
# Если нашли ячейку с ключом возвращаем ее индекс
return idx
if first_deleted_idx is not None:
return first_deleted_idx
# Если массив
raise RuntimeError("Tabulka je plna.")
def load_factor(self):
"""Vráť podiel počtu aktívnych položiek a kapacity."""
factor = self.size / self._capacity
return factor
def __getitem__(self, key):
# Проверяем тип ключа
self.valid_problem(key)
# Находим индекс потенциального местонахождения ключа
idx = self.probe(key)
slot = self.slots[idx]
# Если в ячейке находится активный элемент и его ключ совпадает с искомым возвращаем значение
if slot is not self.empty and slot is not self.deleted and slot[0] == key:
return slot[1]
# Если ключа в таблице нет KeyError
raise KeyError(key)
def __setitem__(self, key, value):
# Проверяем тип ключа
self.valid_problem(key)
# Находим нужный индекс через линейное зондирование
idx = self.probe(key)
slot = self.slots[idx]
# Обновление значения существующего ключа
if slot is not self.empty and slot is not self.deleted and slot[0] == key:
# Просто перезаписываем кортеж (key, value) новым значением
self.slots[idx] = (key, value)
return
# Вставляем новый ключи и проверяем порог заполнения > 0.7
if (self.size + 1) / self._capacity > 0.7:
# Удваиваем таблицу и пересчитываем индексы
old_slots = self.slots
self._capacity = self._capacity * 2
self.slots = [self.empty] * self._capacity
self.size = 0
for slot_item in old_slots:
if slot_item is not self.empty and slot_item is not self.deleted:
k, v = slot_item
idx_new = self.probe(k)
self.slots[idx_new] = (k, v)
self.size += 1
# Перевычисляем индекс для вставляемого ключа уже в новой таблице
idx = self.probe(key)
# Записываем (key, value) в выбранную ячейку
self.slots[idx] = (key, value)
# Увеличиваем количество активных элементов на 1
self.size += 1
def __delitem__(self, key):
# Проверяем тип ключа
self.valid_problem(key)
# Находим индекс элемента через probe
idx = self.probe(key)
slot = self.slots[idx]
# Если ключ найден среди активных элементов
if slot is not self.empty and slot is not self.deleted and slot[0] == key:
# Заменяем пару (key, value) на маркер удаленной ячейки self.deleted
self.slots[idx] = self.deleted
# Уменьшаем количество активных элементов на 1
self.size -= 1
else:
# Если ключ отсутствует KeyError
raise KeyError(key)
def __contains__(self, key):
# Проверяем тип ключа
self.valid_problem(key)
# Находим индекс ячейки
idx = self.probe(key)
slot = self.slots[idx]
# Возвращаем True только если ячейка активна и ключ совпадает
return slot is not self.empty and slot is not self.deleted and slot[0] == key
def __len__(self):
return self.size
def __iter__(self):
active_keys = []
for slot in self.slots:
# Проверка фильтрует незанятые и удаленные ячейки пропускает только те слоты где лежит реальная пара
if slot is not self.empty and slot is not self.deleted:
# Извлекается ключ (элемент с индексом 0) и добавляется в список
active_keys.append(slot[0])
return iter(active_keys)