151 lines
3.9 KiB
Python
151 lines
3.9 KiB
Python
class HashTable:
|
|
_DELETED = object()
|
|
|
|
def __init__(self, capacity=10):
|
|
if type(capacity) is not int:
|
|
raise TypeError("Capacity must be an integer")
|
|
if capacity <= 0:
|
|
raise ValueError("Capacity must be positive")
|
|
|
|
self._capacity = capacity
|
|
self._table = [None] * capacity
|
|
self._size = 0
|
|
|
|
@property
|
|
def capacity(self):
|
|
return self._capacity
|
|
|
|
def hash(self, key):
|
|
if not isinstance(key, str):
|
|
raise TypeError("Key must be a string")
|
|
|
|
h = 0
|
|
for znak in key:
|
|
h = (31 * h + ord(znak)) % self.capacity
|
|
return h
|
|
|
|
def probe(self, key):
|
|
if not isinstance(key, str):
|
|
raise TypeError("Key must be a string")
|
|
|
|
start = self.hash(key)
|
|
first_deleted = None
|
|
|
|
for krok in range(self.capacity):
|
|
index = (start + krok) % self.capacity
|
|
item = self._table[index]
|
|
|
|
if item is None:
|
|
if first_deleted is not None:
|
|
return first_deleted
|
|
return index
|
|
|
|
if item is self._DELETED:
|
|
if first_deleted is None:
|
|
first_deleted = index
|
|
elif item[0] == key:
|
|
return index
|
|
|
|
if first_deleted is not None:
|
|
return first_deleted
|
|
|
|
raise RuntimeError("Hash table is full")
|
|
|
|
def __setitem__(self, key, value):
|
|
if not isinstance(key, str):
|
|
raise TypeError("Key must be a string")
|
|
|
|
try:
|
|
index = self.probe(key)
|
|
except RuntimeError:
|
|
index = None
|
|
|
|
if index is not None:
|
|
item = self._table[index]
|
|
if item is not None and item is not self._DELETED:
|
|
if item[0] == key:
|
|
self._table[index] = (key, value)
|
|
return
|
|
|
|
if (self._size + 1) / self.capacity > 0.7:
|
|
old_table = self._table
|
|
|
|
self._capacity *= 2
|
|
self._table = [None] * self._capacity
|
|
self._size = 0
|
|
|
|
for item in old_table:
|
|
if item is not None and item is not self._DELETED:
|
|
old_key, old_value = item
|
|
new_index = self.probe(old_key)
|
|
self._table[new_index] = (old_key, old_value)
|
|
self._size += 1
|
|
|
|
index = self.probe(key)
|
|
self._table[index] = (key, value)
|
|
self._size += 1
|
|
|
|
def __getitem__(self, key):
|
|
if not isinstance(key, str):
|
|
raise TypeError("Key must be a string")
|
|
|
|
try:
|
|
index = self.probe(key)
|
|
except RuntimeError:
|
|
raise KeyError(key)
|
|
|
|
item = self._table[index]
|
|
|
|
if item is None or item is self._DELETED:
|
|
raise KeyError(key)
|
|
|
|
if item[0] != key:
|
|
raise KeyError(key)
|
|
|
|
return item[1]
|
|
|
|
def __delitem__(self, key):
|
|
if not isinstance(key, str):
|
|
raise TypeError("Key must be a string")
|
|
|
|
try:
|
|
index = self.probe(key)
|
|
except RuntimeError:
|
|
raise KeyError(key)
|
|
|
|
item = self._table[index]
|
|
|
|
if item is None or item is self._DELETED:
|
|
raise KeyError(key)
|
|
|
|
if item[0] != key:
|
|
raise KeyError(key)
|
|
|
|
self._table[index] = self._DELETED
|
|
self._size -= 1
|
|
|
|
def __contains__(self, key):
|
|
if not isinstance(key, str):
|
|
raise TypeError("Key must be a string")
|
|
|
|
try:
|
|
index = self.probe(key)
|
|
except RuntimeError:
|
|
return False
|
|
|
|
item = self._table[index]
|
|
|
|
return (
|
|
item is not None
|
|
and item is not self._DELETED
|
|
and item[0] == key
|
|
)
|
|
|
|
def __len__(self):
|
|
return self._size
|
|
|
|
def __iter__(self):
|
|
for item in self._table:
|
|
if item is not None and item is not self._DELETED:
|
|
yield item[0]
|