173 lines
8.0 KiB
Python
173 lines
8.0 KiB
Python
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) |