diff --git a/hashtable/hashtable.py b/hashtable/hashtable.py index 0205f0ba9..24a26c884 100644 --- a/hashtable/hashtable.py +++ b/hashtable/hashtable.py @@ -7,11 +7,86 @@ def __init__(self, key, value): self.value = value self.next = None + def __eq__(self, other): + if isinstance(other, HashTableEntry): + return self.key == other.key + return False + + def __repr__(self): + return f'HashTableEntry({self.key},{self.value}' + +# class Node: +# def __init__(self,value): +# self.value = value +# self.next = None + +# def __repr__(self): +# return f'Node({self.value})' + + +class LinkedList: + def __init__(self): + self.head = None + + def __repr__(self): # Walking through Linked list and printing it out whats within it. + currStr = "" + curr = self.head + while curr != None: + currStr += f'{str(curr)}' + curr = curr.next + return currStr + #Helper Methods + #O(n) where n is number of nodes + def find(self, value): + cur = self.head + + while cur is not None: + if cur == value: + return cur + + cur = cur.next + + return None + #O(n) where n is number of nodes + def delete(self,value): + # deletes node with given value + curr = self.head + + #Special case if we want to delete the head + if curr.value == value: + self.head = curr.next + curr.next = None + return curr + + prev = None + while curr != None: + if curr.value == value: + prev.next = curr.next + curr.next = None + return curr + else: + prev = curr + curr = curr.next + + return None + #O(1) where n is number of nodes + def insert_at_head(self,node): + node.next = self.head + self.head = node + #O(n) where n is number of nodes because of 'find' + def insert_at_head_or_overwrite(self,node): + #insert node at head or overwrite the node + existing_node = self.find(node.value) + if existing_node != None: + existing_node.value = node.value + return False + else: + self.insert_at_head(node) + return True # Hash table can't have fewer than this many slots MIN_CAPACITY = 8 - class HashTable: """ A hash table that with `capacity` buckets @@ -21,7 +96,10 @@ class HashTable: """ def __init__(self, capacity): - # Your code here + + self.table = [None] * capacity + self.capacity = capacity + self.num_elements = 0 def get_num_slots(self): @@ -34,7 +112,7 @@ def get_num_slots(self): Implement this. """ - # Your code here + return len(self.table) #self.capacity def get_load_factor(self): @@ -43,7 +121,8 @@ def get_load_factor(self): Implement this. """ - # Your code here + # Load factor = num of elements in hash table/ num of slots + return self.num_elements/self.get_num_slots() def fnv1(self, key): @@ -56,15 +135,16 @@ def fnv1(self, key): # Your code here - def djb2(self, key): - """ - DJB2 hash, 32-bit - - Implement this, and/or FNV-1. - """ - # Your code here - - + def djb2(self, key): #http://pythontutor.com/visualize.html#mode=display + hash = 5381 + for x in key: + # ord(x) simply returns the unicode rep of the + # character x + hash = (( hash << 5) + hash) + ord(x) + # Note to clamp the value so that the hash is + # related to the power of 2 + return hash & 0xFFFFFFFF + def hash_index(self, key): """ Take an arbitrary key and return a valid integer index @@ -72,8 +152,9 @@ def hash_index(self, key): """ #return self.fnv1(key) % self.capacity return self.djb2(key) % self.capacity - + def put(self, key, value): + """ Store the value with the given key. @@ -81,8 +162,26 @@ def put(self, key, value): Implement this. """ - # Your code here - + # self.table[self.hash_index(key)] = value --> Array implementation + hash_index = self.hash_index(key) + if self.table[hash_index] != None: + linked_list = self.table[hash_index] + did_add_new_node = linked_list.insert_at_head_or_overwrite(HashTableEntry(key,value)) + + if did_add_new_node: + self.num_elements +=1 + + else: + linked_list = LinkedList() + linked_list.insert_at_head(HashTableEntry(key,value)) + + self.table[hash_index] = linked_list + self.num_elements +=1 + + if self.get_load_factor() > 0.7: + self.resize(self.get_num_slots()*2) + + def delete(self, key): """ @@ -92,8 +191,23 @@ def delete(self, key): Implement this. """ - # Your code here - + # val = self.table[self.hash_index(key)] --> Array implementation + # if val == None: + # print('Value is already None') + # self.table[self.hash_index(key)] = None + hash_index = self.hash_index(key) + if self.table[hash_index] != None: + linked_list = self.table[hash_index] + did_delete_node = linked_list.delete(HashTableEntry(key,None)) + if did_delete_node != None: + self.num_elements -= 1 + if self.get_load_factor() < 0.2: + print(f'load factor is {self.get_load_factor()}') + print(f'self.get_num_slots()/2 is {self.get_num_slots()/2}') + self.resize(self.get_num_slots()/2) + + else: + print(f'This key does not exist!') def get(self, key): """ @@ -103,8 +217,14 @@ def get(self, key): Implement this. """ - # Your code here - + hash_index = self.hash_index(key) + if self.table[hash_index] != None: + linked_list = self.table[hash_index] + + node = linked_list.find(HashTableEntry(key,None)) + if node != None: + return node.value + return None def resize(self, new_capacity): """ @@ -113,9 +233,31 @@ def resize(self, new_capacity): Implement this. """ - # Your code here - - + old_table = self.table + self.table = [None]* int(new_capacity) + self.num_elements = 0 + + for element in old_table: + if element is None: + continue + curr_node = element.head + + while curr_node != None: + temp = curr_node.next + curr_node.next = None # remove the reference since its moving to new table + print(curr_node.key) + hash_index = self.hash_index(curr_node.value.key) # this is where its going to be in new table + print (hash_index) + + if self.table[hash_index] != None: + self.table[hash_index].insert_at_head(curr_node) # Inseert at head if a linked list already exists in this new table + else: + linked_list = LinkedList() # if nothing is present at this index, create new node and add it at head. + linked_list.insert_at_head(curr_node) + self.table[hash_index] = linked_list + + curr_node = temp + self.num_elements += 1 if __name__ == "__main__": ht = HashTable(8) diff --git a/hashtable/test_hashtable_no_collisions.py b/hashtable/test_hashtable_no_collisions.py index a9b755b3d..ec87d7883 100644 --- a/hashtable/test_hashtable_no_collisions.py +++ b/hashtable/test_hashtable_no_collisions.py @@ -11,13 +11,15 @@ class TestHashTable(unittest.TestCase): def test_hash_table_insertion_and_retrieval(self): - ht = HashTable(0x10000) + ht = HashTable(0x10) ht.put("key-0", "val-0") ht.put("key-1", "val-1") ht.put("key-2", "val-2") + return_value = ht.get("key-0") + print (return_value) self.assertTrue(return_value == "val-0") return_value = ht.get("key-1") self.assertTrue(return_value == "val-1")