2
\$\begingroup\$

I implemented a Python Hash Table using only lists. Here is my implementation:

class HashTable:
 # Initialize the table with a fixed size of 54 slots
 def __init__(self):
 self.Table = [None] * 54
 def _get_value(self, key): #This is my hash function.
 # It finds the hash of every string. total % 54
 total = hash(key)
 return total % 54
 def insert(self, key):
 val = self._get_value(key)
 col = False #Collision bool
 index = 0
 if self.Table[val] == None: #Empty slot - turn into list of keys to avoid extra cases
 self.Table[val] = [key]
 else: #Collision - append
 self.Table[val].append(key)
 col = True
 index = len(self.Table[val]) - 1
 return val, col, index
 def delete(self, key):
 val = self._get_value(key)
 if self.Table[val] == None: #Deleting an unexisting element
 return -1, 0
 elif key in self.Table[val]: #This is the O(n) part of the hashtable
 index = self.Table[val].index(key)
 self.Table[val].remove(key)
 return val, index
 else: # No match was found in list, element does not exist
 return -1, 0
 def lookup(self, key):
 val = self._get_value(key)
 if self.Table[val] == None:
 return -1, 0
 else:
 if key in self.Table[val]:
 index = self.Table[val].index(key)
 return val, index
 # No match was found in list, element does not exist
 return -1, 0
 def clear(self):
 self.__init__()

I am using and returning some variables to keep track of collision, values, and indexes to print to the user.

I want to understand how I could improve this implementation - still using only lists (no sets for checking). From a time complexity perspective or even code design, how can I make this more efficient? What design choices were poorly made in my code? What can I improve in this to make it better?

Stephen Rauch
4,31412 gold badges24 silver badges36 bronze badges
asked Dec 17, 2019 at 18:04
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Restructuring and micro-optimization

Hard-coded table size 54 is better defined as class constant:

T_SIZE = 54

Encapsulate the crucial list/table at least as protected property self._Table.

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 in 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)
answered Dec 17, 2019 at 22:19
\$\endgroup\$
0

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.