diff --git a/README.md b/README.md index f766a6189..27d824447 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Hash Tables - +### https://github.com/LambdaSchool/cs-module-project-hash-tables/pull/173 ## Day 1 Task: Implement a basic hash table without collision resolution. @@ -73,10 +73,10 @@ For these, you can use either the built-in `dict` type, or the hashtable you built. (Some of these are easier with `dict` since it's more full-featured.) -* [Lookup Table](applications/lookup_table/) -* [Expensive Sequence](applications/expensive_seq/) -* [Word Count](applications/word_count/) -* [No Duplicates](applications/no_dups/) +* XXXXX[Lookup Table](applications/lookup_table/) +* XXXXX[Expensive Sequence](applications/expensive_seq/) +* XXXXX[Word Count](applications/word_count/) +* XXXXX[No Duplicates](applications/no_dups/) * [Markov Chains](applications/markov/) * [Histogram](applications/histo/) * [Cracking Caesar Ciphers](applications/crack_caesar/) diff --git a/applications/expensive_seq/expensive_seq.py b/applications/expensive_seq/expensive_seq.py index 5c82b8453..1ebad124d 100644 --- a/applications/expensive_seq/expensive_seq.py +++ b/applications/expensive_seq/expensive_seq.py @@ -1,8 +1,19 @@ # Your code here +table = {} + def expensive_seq(x, y, z): - # Your code here + if x <= 0: + return y + z + + if (x, y, z) not in table: + table[(x, y, z)] = ( + expensive_seq(x - 1, y + 1, z) + + expensive_seq(x - 2, y + 2, z * 2) + + expensive_seq(x - 3, y + 3, z * 3) + ) + return table[(x, y, z)] diff --git a/applications/lookup_table/lookup_table.py b/applications/lookup_table/lookup_table.py index 05b7d37fa..fa13c5b47 100644 --- a/applications/lookup_table/lookup_table.py +++ b/applications/lookup_table/lookup_table.py @@ -1,5 +1,6 @@ # Your code here - +import math +import random def slowfun_too_slow(x, y): v = math.pow(x, y) @@ -9,13 +10,25 @@ def slowfun_too_slow(x, y): return v +def build_lookup_table(): + d = {} + + for x in range(2, 14): + for y in range(3, 6): + d[(x, y)] = slowfun_too_slow(x, y) + + return d + +# this ensures that the lookup table is ALREADY BUILT before we call slowfun() in our for loop! +table = build_lookup_table() + def slowfun(x, y): """ Rewrite slowfun_too_slow() in here so that the program produces the same output, but completes quickly instead of taking ages to run. """ # Your code here - + return table[(x, y)] # Do not modify below this line! @@ -23,4 +36,4 @@ def slowfun(x, y): for i in range(50000): x = random.randrange(2, 14) y = random.randrange(3, 6) - print(f'{i}: {x},{y}: {slowfun(x, y)}') + print(f'{i}: {x},{y}: {slowfun(x, y)}') \ No newline at end of file diff --git a/applications/no_dups/no_dups.py b/applications/no_dups/no_dups.py index caa162c8c..ff50c92be 100644 --- a/applications/no_dups/no_dups.py +++ b/applications/no_dups/no_dups.py @@ -1,6 +1,15 @@ def no_dups(s): # Your code here + d = {} + arr = s.split() + output = [] + for word in arr: + if word not in d: + d[word] = 1 + output.append(word) + + return " ".join(output) if __name__ == "__main__": diff --git a/applications/word_count/word_count.py b/applications/word_count/word_count.py index a20546425..277db5961 100644 --- a/applications/word_count/word_count.py +++ b/applications/word_count/word_count.py @@ -1,10 +1,24 @@ -def word_count(s): - # Your code here - +def word_count(string): + separators = '":;,.-+=/\|[]{}()*^&' + + words = string.lower().split() + counts = {} + + for word in words: + word = word.strip(separators) + if not word: + break + if word in counts: + counts[word] += 1 + else: + counts[word] = 1 + + return counts +# Driver program if __name__ == "__main__": print(word_count("")) print(word_count("Hello")) print(word_count('Hello, my cat. And my cat doesn\'t say "hello" back.')) - print(word_count('This is a test of the emergency broadcast network. This is only a test.')) \ No newline at end of file + print(word_count('This is a test of the emergency broadcast network. This is only a test.')) diff --git a/hashtable/hashtable.py b/hashtable/hashtable.py index 0205f0ba9..f2dcb57d4 100644 --- a/hashtable/hashtable.py +++ b/hashtable/hashtable.py @@ -8,6 +8,58 @@ def __init__(self, key, value): self.next = None +class LinkedList: + def __init__(self): + self.head = None + + def __repr__(self): + current_str = "" + current = self.head + while current is not None: + current_str += f"{str(current.value)} -> " + current = current.next + return current_str + + def add_to_head(self, node): + node.next = self.head + self.head = node + + def add_to_head_or_overwrite(self, node): + existing_node = self.find(node.key) + if existing_node is not None: + existing_node.key = node.key + else: + self.add_to_head(node) + + def find(self, key): + current = self.head + while current is not None: + if current.key == key: + return current.value + current = current.next + return None + + def delete(self, key): + current = self.head + + if current.key == key: + self.head = current.next + + prev = current + current = current.next + + while current is not None: + if current.key == key: + prev.next = current.next + current.next = None + return current.value + else: + prev = current + current = current.next + + return None + + # Hash table can't have fewer than this many slots MIN_CAPACITY = 8 @@ -16,12 +68,16 @@ class HashTable: """ A hash table that with `capacity` buckets that accepts string keys - Implement this. """ def __init__(self, capacity): # Your code here + self.capacity = capacity + self.table = [None] * capacity + + for i in range(0, len(self.table)): + self.table[i] = LinkedList() def get_num_slots(self): @@ -29,40 +85,63 @@ def get_num_slots(self): Return the length of the list you're using to hold the hash table data. (Not the number of items stored in the hash table, but the number of slots in the main list.) - One of the tests relies on this. - + + One of the tests relies on this. if froggy do tonight 🤣 + Implement this. """ # Your code here + return len(self.table) def get_load_factor(self): """ Return the load factor for this hash table. - Implement this. """ # Your code here + count = 0 + + for i in range(0, len(self.table)): + current = self.table[i].head + while current is not None: + count += 1 + current = current.next + + load_factor = count / self.get_num_slots() + + if load_factor > 0.7: + self.resize(self.capacity * 2) + elif load_factor < 0.2: + if self.capacity // 2 < 8: + self.resize(8) + else: + self.resize(self.capacity // 2) + + return load_factor def fnv1(self, key): """ FNV-1 Hash, 64-bit - Implement this, and/or DJB2. """ # Your code here + pass def djb2(self, key): """ DJB2 hash, 32-bit - Implement this, and/or FNV-1. """ # Your code here + hash = 5381 + for c in key: + hash = (hash * 33) + ord(c) + return hash def hash_index(self, key): @@ -73,48 +152,60 @@ 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. - Hash collisions should be handled with Linked List Chaining. - Implement this. """ # Your code here + new_entry = HashTableEntry(key, value) + self.table[self.hash_index(key)].add_to_head(new_entry) def delete(self, key): """ Remove the value stored with the given key. - Print a warning if the key is not found. - Implement this. """ # Your code here + return self.table[self.hash_index(key)].delete(key) def get(self, key): """ Retrieve the value stored with the given key. - Returns None if the key is not found. - Implement this. """ # Your code here + if self.table[self.hash_index(key)].head is not None: + return self.table[self.hash_index(key)].find(key) + else: + return None def resize(self, new_capacity): """ Changes the capacity of the hash table and rehashes all key/value pairs. - Implement this. """ # Your code here + old_capacity = self.capacity + self.capacity = new_capacity + + for i in range(old_capacity, new_capacity): + self.table.append(None) + self.table[i] = LinkedList() + for i in range(0, old_capacity): + current_node = self.table[i].head + while current_node is not None: + self.put(current_node.key, current_node.value) + current_node = current_node.next if __name__ == "__main__": @@ -146,8 +237,8 @@ def resize(self, new_capacity): print(f"\nResized from {old_capacity} to {new_capacity}.\n") - # Test if data intact after resizing + # # Test if data intact after resizing for i in range(1, 13): print(ht.get(f"line_{i}")) - print("") + print("") \ No newline at end of file