def resize_table(self, new_capacity: int) -> None: """ Changes the capacity of the internal hash table. All existing key / value pairs must remain in the new hash map and all hash table links must be rehashed. If new_capacity is less than 1, this method should do nothing. """ # create a new table with the new capacity if new_capacity is greater than 1 if new_capacity > 0: resized_table = DynamicArray() for _ in range(new_capacity): resized_table.append(LinkedList()) # assign the current hash table in temp_table & reassign self.buckets to the resized_table temp_table = self.buckets self.buckets = resized_table self.capacity = new_capacity self.size = 0 # Walk through each bucket in the temp_table and visit each element for i in range(temp_table.length()): temp_bucket = temp_table.get_at_index(i) if temp_bucket.length() > 0: cur = temp_bucket.head # insert the element into the new hash table at the rehashed index while cur: self.put(cur.key, cur.value) cur = cur.next
def __init__(self, capacity: int, function) -> None: """ Init new HashMap based on DA with SLL for collision resolution DO NOT CHANGE THIS METHOD IN ANY WAY """ self.buckets = DynamicArray() for _ in range(capacity): self.buckets.append(LinkedList()) self.capacity = capacity self.hash_function = function self.size = 0
def __init__(self, start_heap=None): """ Initializes a new MinHeap DO NOT CHANGE THIS METHOD IN ANY WAY """ self.heap = DynamicArray() # populate MH with initial values (if provided) # before using this feature, implement add() method if start_heap: for node in start_heap: self.add(node)
def get_keys(self) -> DynamicArray: """ Returns a Dynamic Array that contains all keys stored in your hash map. The order of the keys in the DA does not matter. """ key_arr = DynamicArray() # Traverse the hash map. If a bucket is not empty, walk through the chained linked list # to retrieve each element's key for i in range(self.buckets.length()): bucket = self.buckets.get_at_index(i) if bucket is not None: cur = bucket.head while cur is not None: key_arr.append(cur.key) cur = cur.next return key_arr
def build_heap(self, da: DynamicArray) -> None: """ Receives a dynamic array with objects in any order and builds a proper MinHeap from them. Current content of the MinHeap is lost. Runtime complexity must be O(N). """ self.heap = DynamicArray() counter1 = 0 # copy elements in da to the MinHeap array (O(N)) for i in range(da.length()): self.heap.append(da.get_at_index(i)) last_index = self.heap.length() - 1 first_nonleaf = int((last_index - 1) / 2) # Start with the first non-leaf node in the tree and continue swapping nodes until # the tree is a valid heap (O(N)) for pos in range(first_nonleaf, -1, -1): self.percolate_down(pos)
class MinHeap: def __init__(self, start_heap=None): """ Initializes a new MinHeap DO NOT CHANGE THIS METHOD IN ANY WAY """ self.heap = DynamicArray() # populate MH with initial values (if provided) # before using this feature, implement add() method if start_heap: for node in start_heap: self.add(node) def __str__(self) -> str: """ Return MH content in human-readable form DO NOT CHANGE THIS METHOD IN ANY WAY """ return 'HEAP ' + str(self.heap) def is_empty(self) -> bool: """ Return True if no elements in the heap, False otherwise DO NOT CHANGE THIS METHOD IN ANY WAY """ return self.heap.length() == 0 def add(self, node: object) -> None: """ Adds a new object to the MinHeap maintaining heap property. Runtime complexity of its implementation must be O(logN) """ self.heap.append(node) cur_index = self.heap.length() - 1 parent_index = int((cur_index - 1) / 2) # the added node percolates up the tree until it reaches the root or parent node's value is less while cur_index > 0 and self.heap.get_at_index( parent_index) > self.heap.get_at_index(cur_index): self.heap.swap(parent_index, cur_index) cur_index = parent_index parent_index = int((cur_index - 1) / 2) def get_min(self) -> object: """ Returns an object with a minimum key without removing it from the heap. If heap is empty, the method raises an Exception. Runtime complexity must be O(1). """ if self.heap.length() == 0: raise MinHeapException return self.heap.get_at_index(0) def remove_min(self) -> object: """ Returns an object with a minimum key and removes it from the heap. If the heap is empty, the method raises an Exception. Runtime complexity of this implementation must be O(logN). """ if self.heap.length() == 0: raise MinHeapException min_value = self.heap.get_at_index(0) self.heap.swap(0, self.heap.length() - 1) # replace the min node with the last filled node self.heap.pop() # remove the min node self.percolate_down(0) # call percolate_down helper method return min_value def percolate_down(self, cur_index): """ Helper method to percolate node down the tree until the subtree is a valid heap Runtime complexity is O(logN) """ while cur_index >= 0: swap_index = -1 right_index = 2 * cur_index + 2 left_index = 2 * cur_index + 1 # Case where right child is less than the current node if right_index < self.heap.length() and self.heap.get_at_index( right_index) < self.heap.get_at_index(cur_index): # check if the left child is less than or equal to the right child. parent node will swap with left child if self.heap.get_at_index( left_index) <= self.heap.get_at_index(right_index): swap_index = left_index else: swap_index = right_index # Case where left child is less than the current node elif left_index < self.heap.length() and self.heap.get_at_index( left_index) < self.heap.get_at_index(cur_index): swap_index = left_index # swap the current node and the smaller child node (swap_index > 0 means that a swap is needed) if swap_index >= 0: self.heap.swap(cur_index, swap_index) cur_index = swap_index def build_heap(self, da: DynamicArray) -> None: """ Receives a dynamic array with objects in any order and builds a proper MinHeap from them. Current content of the MinHeap is lost. Runtime complexity must be O(N). """ self.heap = DynamicArray() counter1 = 0 # copy elements in da to the MinHeap array (O(N)) for i in range(da.length()): self.heap.append(da.get_at_index(i)) last_index = self.heap.length() - 1 first_nonleaf = int((last_index - 1) / 2) # Start with the first non-leaf node in the tree and continue swapping nodes until # the tree is a valid heap (O(N)) for pos in range(first_nonleaf, -1, -1): self.percolate_down(pos)
print("-------------------") h = MinHeap(['fish', 'bird']) print(h) for value in ['monkey', 'zebra', 'elephant', 'horse', 'bear']: h.add(value) print(h) print("\nPDF - get_min example 1") print("-----------------------") h = MinHeap(['fish', 'bird']) print(h) print(h.get_min(), h.get_min()) print("\nPDF - remove_min example 1") print("--------------------------") h = MinHeap([1, 10, 2, 9, 3, 8, 4, 7, 5, 6]) while not h.is_empty(): print(h, end=' ') print(h.remove_min()) print("\nPDF - build_heap example 1") print("--------------------------") da = DynamicArray([100, 20, 6, 200, 90, 150, 300]) h = MinHeap(['zebra', 'apple']) print(h) h.build_heap(da) print(h) da.set_at_index(0, 500) print(da) print(h)
class HashMap: def __init__(self, capacity: int, function) -> None: """ Init new HashMap based on DA with SLL for collision resolution DO NOT CHANGE THIS METHOD IN ANY WAY """ self.buckets = DynamicArray() for _ in range(capacity): self.buckets.append(LinkedList()) self.capacity = capacity self.hash_function = function self.size = 0 def __str__(self) -> str: """ Return content of hash map t in human-readable form DO NOT CHANGE THIS METHOD IN ANY WAY """ out = '' for i in range(self.buckets.length()): list = self.buckets.get_at_index(i) out += str(i) + ': ' + str(list) + '\n' return out def new_index(self, key): """Helper method to calculate the index to insert an element to the hash map""" hash = self.hash_function(key) return abs(hash) % self.capacity def clear(self) -> None: """ Clears the contents of the hash map. It does not change the underlying hash table capacity. """ for i in range(self.buckets.length()): if self.buckets.get_at_index(i).length() > 0: self.size -= self.buckets.get_at_index(i).length() # reduce self.size by length of linked list self.buckets.set_at_index(i, LinkedList()) def get(self, key: str) -> object: """ Returns the value associated with the given key. If the key is not in the hash map, the method returns none. """ bucket = self.buckets.get_at_index(self.new_index(key)) if bucket.contains(key) is not None: return bucket.contains(key).value else: return None def put(self, key: str, value: object) -> None: """ Updates the key / value pair in the hash map. If a given key already exists in the hash map, its associated value should be replaced with the new value. If a given key is not in the hash map, a key / value pair should be added. """ bucket = self.buckets.get_at_index(self.new_index(key)) # if the same key exists, replace the value of the node with the new value if bucket.contains(key) is not None: bucket.contains(key).value = value # if the key doesn't exist, insert the element as a new node else: bucket.insert(key, value) self.size += 1 def remove(self, key: str) -> None: """ Removes the given key and its associated value from the hash map. If a given key is not in the hash map, the method does nothing (no exception needs to be raised). """ bucket = self.buckets.get_at_index(self.new_index(key)) if bucket.contains(key) is not None: bucket.remove(key) self.size -= 1 def contains_key(self, key: str) -> bool: """ Returns True if the given key is in the hash map, otherwise it returns False. An empty hash map does not contain any keys. """ bucket = self.buckets.get_at_index(self.new_index(key)) if bucket.contains(key) is not None: return True return False def empty_buckets(self) -> int: """ Returns a number of empty buckets in the hash table. """ empty_buckets = 0 for index in range(self.buckets.length()): if self.buckets.get_at_index(index).length() == 0: empty_buckets += 1 return empty_buckets def table_load(self) -> float: """ Returns the current hash table load factor. """ return self.size / self.capacity def resize_table(self, new_capacity: int) -> None: """ Changes the capacity of the internal hash table. All existing key / value pairs must remain in the new hash map and all hash table links must be rehashed. If new_capacity is less than 1, this method should do nothing. """ # create a new table with the new capacity if new_capacity is greater than 1 if new_capacity > 0: resized_table = DynamicArray() for _ in range(new_capacity): resized_table.append(LinkedList()) # assign the current hash table in temp_table & reassign self.buckets to the resized_table temp_table = self.buckets self.buckets = resized_table self.capacity = new_capacity self.size = 0 # Walk through each bucket in the temp_table and visit each element for i in range(temp_table.length()): temp_bucket = temp_table.get_at_index(i) if temp_bucket.length() > 0: cur = temp_bucket.head # insert the element into the new hash table at the rehashed index while cur: self.put(cur.key, cur.value) cur = cur.next def get_keys(self) -> DynamicArray: """ Returns a Dynamic Array that contains all keys stored in your hash map. The order of the keys in the DA does not matter. """ key_arr = DynamicArray() # Traverse the hash map. If a bucket is not empty, walk through the chained linked list # to retrieve each element's key for i in range(self.buckets.length()): bucket = self.buckets.get_at_index(i) if bucket is not None: cur = bucket.head while cur is not None: key_arr.append(cur.key) cur = cur.next return key_arr