Restructuring and micro-optimization
Hard-coded table size 54 is better defined as class constant:
T_SIZE = 54
Calling self._get_value(key) and the condition if self._Table[val] == None are repeated in most crucial methods. To reduce that noisy repetition an additional method can be defined which will return a tuple of calculated value val and is_empty ("empty slot" flag):
def _check_value(self, key):
val = self._get_value(key)
is_empty = self._Table[val] is None
return val, is_empty
It doesn't make sense to construct if ... else conditional if the 1st if branch return 's immediately.
Both delete and lookup methods, on existing item, will perform 2 access/lookup operations on self._Table[val]:
key in self._Table[val]
self._Table[val].index(key)
To reduce access operations there we can apply try/except trick and returning the needed result in each separate block. See the final implementation below:
class HashTable:
T_SIZE = 54
# Initialize the table with a fixed size
def __init__(self):
self._Table = [None] * HashTable.T_SIZE
def _get_value(self, key):
total = hash(key)
return total % HashTable.T_SIZE
def _check_value(self, key):
val = self._get_value(key)
is_empty = self._Table[val] is None
return val, is_empty
def insert(self, key):
val, is_empty = self._check_value(key)
col = False # Collision flag
index = 0
if is_empty: # Empty slot - turn into list of keys to avoid extra cases
self._Table[val] = [key]
else:
self._Table[val].append(key) # Collision - append
col = True
index = len(self._Table[val]) - 1
return val, col, index
def delete(self, key):
val, is_empty = self._check_value(key)
if is_empty: # Deleting unexisting element
return -1, 0
try:
index = self._Table[val].index(key)
self._Table[val].remove(key)
return val, index
except ValueError: # No match was found in list, element does not exist
return -1, 0
def lookup(self, key):
val, is_empty = self._check_value(key)
if is_empty:
return -1, 0
try:
index = self._Table[val].index(key)
return val, index
except ValueError: # No match was found in list, element does not exist
return -1, 0
def clear(self):
self.__init__()
In case if _get_value method won't be used as standalone context - you may easily inline it into _check_value method.
Sample usage:
h = HashTable()
h.insert('abc')
h.insert('abc')
print(h.lookup('abc'))
print(h.lookup('1abc'))
print(h.delete('abc'))
print(h.delete('abc'))
print(h.delete('abc'))
The output (consecutively):
(8, 0)
(-1, 0)
(8, 0)
(8, 0)
(-1, 0)