diff --git a/applications/expensive_seq/README.md b/applications/expensive_seq/README.md index 20ad36c17..78b7595af 100644 --- a/applications/expensive_seq/README.md +++ b/applications/expensive_seq/README.md @@ -25,5 +25,8 @@ ends. Hint: Va Clguba, n qvpg xrl pna or nal vzzhgnoyr glcr... vapyhqvat n ghcyr. +In Python, a dict key can be any immutable type... including a +tuple. + (That's encrypted with ROT13--Google `rot13 decoder` to decode it if you want the hint.) \ No newline at end of file diff --git a/applications/expensive_seq/expensive_seq.py b/applications/expensive_seq/expensive_seq.py index 5c82b8453..21e73009a 100644 --- a/applications/expensive_seq/expensive_seq.py +++ b/applications/expensive_seq/expensive_seq.py @@ -3,7 +3,7 @@ def expensive_seq(x, y, z): # Your code here - + if __name__ == "__main__": diff --git a/applications/lookup_table/lookup_table.py b/applications/lookup_table/lookup_table.py index 05b7d37fa..122893483 100644 --- a/applications/lookup_table/lookup_table.py +++ b/applications/lookup_table/lookup_table.py @@ -1,5 +1,7 @@ # Your code here +import random +prevV = {} def slowfun_too_slow(x, y): v = math.pow(x, y) @@ -7,6 +9,7 @@ def slowfun_too_slow(x, y): v //= (x + y) v %= 982451653 + prevV[x, y] = v return v def slowfun(x, y): @@ -15,7 +18,11 @@ def slowfun(x, y): output, but completes quickly instead of taking ages to run. """ # Your code here + if x and y in prevV: + return prevV[x, y] + else: + slowfun_too_slow(x, y) # Do not modify below this line! diff --git a/applications/no_dups/no_dups.py b/applications/no_dups/no_dups.py index caa162c8c..686cbe151 100644 --- a/applications/no_dups/no_dups.py +++ b/applications/no_dups/no_dups.py @@ -1,6 +1,6 @@ def no_dups(s): # Your code here - + pass if __name__ == "__main__": diff --git a/applications/word_count/word_count.py b/applications/word_count/word_count.py index a20546425..c3b433c6a 100644 --- a/applications/word_count/word_count.py +++ b/applications/word_count/word_count.py @@ -1,6 +1,16 @@ def word_count(s): # Your code here + counts = {} + words = s.lower() + lowerWords = words.split() + for word in lowerWords: + if word in counts: + counts[word] += 1 + else: + counts[word] = 1 + + return counts if __name__ == "__main__": diff --git a/hashtable/hashtable.py b/hashtable/hashtable.py index 0205f0ba9..eaa1d2d25 100644 --- a/hashtable/hashtable.py +++ b/hashtable/hashtable.py @@ -1,3 +1,6 @@ +from linked_list import Node +from linked_list import LinkedList + class HashTableEntry: """ Linked List hash table key/value pair @@ -7,6 +10,11 @@ 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 + # Hash table can't have fewer than this many slots MIN_CAPACITY = 8 @@ -21,7 +29,12 @@ class HashTable: """ def __init__(self, capacity): - # Your code here + # self.table = [None] * capacity + # self.capacity = capacity + + self.table = [None] * capacity + self.capacity = capacity + self.num_elements = 0 def get_num_slots(self): @@ -35,6 +48,7 @@ def get_num_slots(self): Implement this. """ # Your code here + return len(self.table) def get_load_factor(self): @@ -44,6 +58,8 @@ def get_load_factor(self): Implement this. """ # Your code here + # load factor = num of elements in the hash table / num slots + return self.num_elements / self.get_num_slots() def fnv1(self, key): @@ -54,8 +70,7 @@ def fnv1(self, key): """ # Your code here - - + def djb2(self, key): """ DJB2 hash, 32-bit @@ -63,7 +78,10 @@ def djb2(self, key): Implement this, and/or FNV-1. """ # Your code here - + hash = 5381 + for x in key: + hash = ((hash << 5) + hash) + ord(x) + return hash & 0xFFFFFFFF def hash_index(self, key): """ @@ -82,6 +100,37 @@ def put(self, key, value): Implement this. """ # Your code here + # self.table[self.hash_index(key)] = value + + # index = self.hash_index(key) + # current_entry = self.table[index] + + # while current_entry is not None and current_entry.key != key: + # current_entry = current_entry.next + # if current_entry is not None: + # current_entry.value = value + # else: + # new_entry = HashTableEntry(key, value) + # new_entry.next = self.table[index] + # self.table[index] = new_entry + + # self.item_count += 1 + # if self.get_load_factor() > 0.7: + # self.resize(self.capacity * 2) + + 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(Node(HashTableEntry(key, value))) + if did_add_new_node: + self.num_elements += 1 + else: + linked_list = LinkedList() + linked_list.insert_at_head(Node(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): @@ -93,6 +142,21 @@ def delete(self, key): Implement this. """ # Your code here + # value = self.table[self.hash_index(key)] + # if value == 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: + self.resize(self.get_num_slots() / 2) + else: + print("Warning: node not found") def get(self, key): @@ -104,6 +168,23 @@ def get(self, key): Implement this. """ # Your code here + # return self.table[self.hash_index(key)] + # index = self.hash_index(key) + + # current_entry = self.table[index] + + # while current_entry is not None: + # if(current_entry.key == key): + # return current_entry.value + # current_entry = current_entry.next + + 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.value + return None def resize(self, new_capacity): @@ -113,8 +194,28 @@ 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 + hash_index = self.hash_index(curr_node.value.key) + + if self.table[hash_index] != None: + self.table[hash_index].insert_at_head(curr_node) + else: + linked_list = LinkedList() + linked_list.insert_at_head(curr_node) + self.table[hash_index] = linked_list + + curr_node = temp + self.num_elements += 1 if __name__ == "__main__": diff --git a/hashtable/linked_list.py b/hashtable/linked_list.py new file mode 100644 index 000000000..88e268713 --- /dev/null +++ b/hashtable/linked_list.py @@ -0,0 +1,79 @@ +class Node: + def __init__(self, value): + self.value = value + self.next = None + +class LinkedList: + def __init__(self): + self.head = None + + def __repr__(self): + currStr = "" + curr = self.head + while curr != None: + currStr += f'{str(curr.value)} ->' + curr = curr.next + return currStr + + # return node w/ value + # runtime: O(n) where n = number nodes + def find(self, value): + curr = self.head + while curr != None: + if curr.value == value: + return curr + curr = curr.next + return None + + # deletes node w/ given value then return that node + # runtime: O(n) where n = number of nodes + def delete(self, value): + cur = self.head + + # special case if we need to delete the head + if cur.value == value: + self.head = cur.next + return cur + + prev = None + cur = cur.next + + while cur != None: + if cur.value == value: + prev.next = cur.next + cur.next = None + return cur + else: + prev = cur + cur = cur.next + + return None + + # insert node at head of list + # runtime: O(1) + def insert_at_head(self, node): + node.next = self.head + self.head = node + + # overwrite node or insert node at head + # runtime: O(n) + def insert_at_head_or_overwrite(self, node): + existingNode = self.find(node.value) # O(n) + if existingNode != None: + existingNode.value = node.value + return False + else: + self.insert_at_head(node) # O(1) + return True + + def add_to_tail(self, value): + # 0. create new node from value + new_node = Node(value, None) + # 1. check if list is empty + if not self.head: + # if list is empty, set both head and tail to new node + self.head = new_node + self.tail = new_node + else: + self.tail.set_next(new_node) + self.tail = new_node \ No newline at end of file