class HashTable: _DELETED = object() def __init__(self, capacity=10): if type(capacity) != 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 type(key) != str: raise TypeError h = 0 for char in key: h = (31 * h + ord(char)) % self.capacity return h def probe(self, key): if type(key) != str: raise TypeError start = self.hash(key) deleted = None for i in range(self.capacity): index = (start + i) % self.capacity item = self._table[index] if item is None: if deleted is not None: return deleted return index if item is self._DELETED: if deleted is None: deleted = index elif item[0] == key: return index if deleted is not None: return deleted raise RuntimeError def __setitem__(self, key, value): if type(key) != str: raise TypeError try: index = self.probe(key) 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 except RuntimeError: pass if (self._size + 1) / self.capacity > 0.7: old_table = self._table self._capacity = 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: index = self.probe(item[0]) self._table[index] = item self._size += 1 index = self.probe(key) self._table[index] = (key, value) self._size += 1 def __getitem__(self, key): if type(key) != str: raise TypeError index = self.probe(key) item = self._table[index] if item is None or item is self._DELETED: raise KeyError(key) return item[1] def __delitem__(self, key): if type(key) != str: raise TypeError index = self.probe(key) item = self._table[index] if item is None or item is self._DELETED: raise KeyError(key) self._table[index] = self._DELETED self._size -= 1 def __contains__(self, key): if type(key) != str: raise TypeError try: index = self.probe(key) item = self._table[index] if item is not None and item is not self._DELETED: if item[0] == key: return True return False except RuntimeError: return False def __len__(self): return self._size def __iter__(self): keys = [] for item in self._table: if item is not None and item is not self._DELETED: keys.append(item[0]) return iter(keys) def load_factor(self): return self._size / self.capacity