Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions src/hash_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
class HashNode:
"""Для хранения пары ключ-значения в цепочке"""

def __init__(self, key, value):
self.key = key
self.value = value
self.next = None


class HashTable:
"""Хеш-таблица с использованием метода цепочек для разрешения коллизий"""

def __init__(self, size=128): # инициализация хеш-таблицы
self.size = size
self.table = [None] * size
self.count = 0

def _hash(self, key): # хеш-функция для строковых ключей

if not isinstance(key, str):
raise TypeError("Ключ должен быть строкой")

hash.value = 0
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hash_value

for i in key:
hash_value = (hash_value * 31 + ord(i)) % self.size

return hash_value

def get(self, key, default=None): # получение значения по ключу
index = self._hash(key)
current = self.table[index]

# ищем ключ в цепочке
while current:
if current.key == key:
return current.value
current = current.next

# Ключ не найден
return default


def put(self, key, value): # добавление значения
index = self._hash(key)

#если ячейка пуста, создаем новый узел
if self.table[index] is None:
self.table[index] = HashNode(key, value)
self.count += 1
return

#ищем ключ в цепочке
current = self.table[index]
while current:
if current.key == key:
current.value = value
return
if current.next is None:
break
current = current.next

# если ключ не найден, добавляем новый узел в конец цепочки
current.next = HashNode(key, value)
self.count += 1

def remove(self, key):
"""Удаление пары ключ-значение"""
index = self._hash(key)
current = self.table[index]
pr = None

# ищем ключ в цепочке
while current:
if current.key == key:
# удаляем узел из цепочки
if pr:
pr.next = current.next
else:
self.table[index] = current.next
self.count -= 1
return current.value

pr = current
current = current.next

#ключ не найден
raise KeyError("Ключ не найден")

def __contains__(self, key): # проверка наличия ключа в таблице
return self.get(key) is not None

def __len__(self): # количество элементов в таблице
return self.count

def keys(self):
"""Генератор всех ключей в таблице"""
for head in self.table:
current = head
while current:
yield current.key
current = current.next

def values(self):
"""Генератор всех значений в таблице"""
for head in self.table:
current = head
while current:
yield current.value
current = current.next

def items(self):
"""Генератор всех пар ключ-значение в таблице"""
for head in self.table:
current = head
while current:
yield (current.key, current.value)
current = current.next

def clear(self):
"""Очистка таблицы"""
self.table = [None] * self.size
self.count = 0






136 changes: 136 additions & 0 deletions src/tests_hash_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import pytest
from hash_table import HashTable


class TestHashTable:
"""Тесты для класса HashTable"""

def test_init(self): # тест инициализации таблицы
ht = HashTable()
assert len(ht) == 0
assert ht.size == 128

def test_put_and_get(self): # тест добавления и получения элементов
ht = HashTable()

# Добавление элементов
ht.put("key1", "value1")
ht.put("key2", 123)
ht.put("key3", [1, 2, 3])

# Проверка получения
assert ht.get("key1") == "value1"
assert ht.get("key2") == 123
assert ht.get("key3") == [1, 2, 3]
assert len(ht) == 3

def test_update_value(self):
"""Тест обновления значения существующего ключа"""
ht = HashTable()

ht.put("key1", "old_value")
assert ht.get("key1") == "old_value"

ht.put("key1", "new_value")
assert ht.get("key1") == "new_value"
assert len(ht) == 1 # Количество не должно измениться

def test_get_nonexistent(self):
"""Тест получения несуществующего ключа"""
ht = HashTable()

assert ht.get("nonexistent") is None
assert ht.get("nonexistent", "default") == "default"

def test_remove(self):
"""Тест удаления элементов"""
ht = HashTable()

ht.put("key1", "value1")
ht.put("key2", "value2")

# Удаление существующего ключа
removed_value = ht.remove("key1")
assert removed_value == "value1"
assert len(ht) == 1
assert ht.get("key1") is None

# Удаление несуществующего ключа
with pytest.raises(KeyError):
ht.remove("nonexistent")

def test_contains(self):
"""Тест проверки наличия ключа"""
ht = HashTable()

ht.put("key1", "value1")

assert "key1" in ht
assert "nonexistent" not in ht

def test_collision_handling(self):
"""Тест обработки коллизий"""
ht = HashTable(size=5) # Маленький размер для теста коллизий

# Добавляем ключи, которые могут вызывать коллизии
ht.put("ab", "value1")
ht.put("ba", "value2") # Может создать коллизию

# Проверяем, что оба значения доступны
assert ht.get("ab") == "value1"
assert ht.get("ba") == "value2"

# Удаляем один из них
ht.remove("ab")
assert ht.get("ab") is None
assert ht.get("ba") == "value2"

def test_clear(self):
"""Тест очистки таблицы"""
ht = HashTable()

ht.put("key1", "value1")
ht.put("key2", "value2")

assert len(ht) == 2

ht.clear()
assert len(ht) == 0
assert ht.get("key1") is None
assert ht.get("key2") is None

def test_keys_values_items(self):
"""Тест методов keys, values, items"""
ht = HashTable()

ht.put("key1", "value1")
ht.put("key2", "value2")
ht.put("key3", "value3")

# Проверяем keys
keys = list(ht.keys())
assert set(keys) == {"key1", "key2", "key3"}

# Проверяем values
values = list(ht.values())
assert set(values) == {"value1", "value2", "value3"}

# Проверяем items
items = dict(ht.items())
assert items == {"key1": "value1", "key2": "value2", "key3": "value3"}

def test_hash_function(self):
"""Тест хэш-функции"""
ht = HashTable()

# Хэш должен быть в пределах размера таблицы
for key in ["test", "hello", "world", "python"]:
hash_val = ht._hash(key)
assert 0 <= hash_val < ht.size

# Проверяем ошибку для нестрокового ключа
with pytest.raises(TypeError):
ht._hash(123)

if __name__ == "__main__":
pytest.main([__file__, "-v"])