From d1da0fea433f9feec4821218bc66699007120b61 Mon Sep 17 00:00:00 2001 From: Ken Diehl Date: Wed, 28 Oct 2020 22:58:21 -0400 Subject: [PATCH 1/6] Part 1 MVP complete, all tests passing --- hashtable/hashtable.py | 42 +++++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/hashtable/hashtable.py b/hashtable/hashtable.py index 0205f0ba9..a8e580dcf 100644 --- a/hashtable/hashtable.py +++ b/hashtable/hashtable.py @@ -21,7 +21,8 @@ class HashTable: """ def __init__(self, capacity): - # Your code here + self.capacity = capacity + self.ht_arr = [None] * self.capacity def get_num_slots(self): @@ -34,13 +35,13 @@ def get_num_slots(self): Implement this. """ - # Your code here + return len(self.ht_arr) def get_load_factor(self): """ Return the load factor for this hash table. - + TODO: Skip Implement this. """ # Your code here @@ -55,14 +56,18 @@ def fnv1(self, key): # Your code here - + # * Visualization of djb2: + # * http://pythontutor.com/visualize.html#code=def%20djb2%28key%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20DJB2%20hash,%2032-bit%0A%0A%20%20%20%20%20%20%20%20Implement%20this,%20and/or%20FNV-1.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20hash%20%3D%205381%0A%20%20%20%20%20%20%20%20for%20x%20in%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20hash%20%3D%20%28%28hash%20%3C%3C%205%29%20%2B%20hash%29%20%2B%20ord%28x%29%0A%20%20%20%20%20%20%20%20return%20hash%20%26%200xFFFFFFFF%0A%20%20%20%20%20%20%20%20%0Adjb2%28%22hello%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false def djb2(self, key): """ DJB2 hash, 32-bit 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): @@ -81,8 +86,12 @@ def put(self, key, value): Implement this. """ - # Your code here - + # ? Put + # ? 1. Hash our string/key, get out a number + # ? 2. Take this number and modulo it by the length of the array + # ? 3. This new number can be used as an index, so put the value at that index in our array + + self.ht_arr[self.hash_index(key)] = value def delete(self, key): """ @@ -92,8 +101,11 @@ def delete(self, key): Implement this. """ - # Your code here - + # ? Delete: find the value, then set to None + if self.ht_arr[self.hash_index(key)] is None: + print(f"The key '{key}' does not exist.'") + else: + self.ht_arr[self.hash_index(key)] = None def get(self, key): """ @@ -103,14 +115,22 @@ def get(self, key): Implement this. """ - # Your code here + # ? Get + # ? 1. Hash our string/key, string --> number + # ? 2. Mod this number by length of array + # ? 3. Use this modded number / index to get the value there + + if self.ht_arr[self.hash_index(key)] is None: + print(f"The key '{key}' does not exist.'") + else: + return self.ht_arr[self.hash_index(key)] def resize(self, new_capacity): """ Changes the capacity of the hash table and rehashes all key/value pairs. - + TODO: Skip Implement this. """ # Your code here From 2adde5058b1ea4a45d1385deeb0c1a65a5b3c7a1 Mon Sep 17 00:00:00 2001 From: Ken Diehl Date: Mon, 2 Nov 2020 18:37:20 -0500 Subject: [PATCH 2/6] Completed hashtable refactor to handle collisions and resizing --- hashtable/hashtable.py | 212 ++++++++++++++++++++++++----------------- 1 file changed, 127 insertions(+), 85 deletions(-) diff --git a/hashtable/hashtable.py b/hashtable/hashtable.py index a8e580dcf..3d272702f 100644 --- a/hashtable/hashtable.py +++ b/hashtable/hashtable.py @@ -2,6 +2,7 @@ class HashTableEntry: """ Linked List hash table key/value pair """ + def __init__(self, key, value): self.key = key self.value = value @@ -11,130 +12,171 @@ def __init__(self, key, value): # Hash table can't have fewer than this many slots MIN_CAPACITY = 8 +# ? Different strategies for handling collisions: +# ? - Chaining: array of linked lists with 1 LL per index, each node.next points to second element +# ? - Array of arrays, with one array per index, just append +# ? - Disallow collisions +# ? - Open addressing. Linear probing, quadratic probing. [None, 'hello', 'world', None] -class HashTable: - """ - A hash table that with `capacity` buckets - that accepts string keys - Implement this. - """ +class HashTable: + # ? A hash table that with `capacity` buckets + # ? that accepts string keys def __init__(self, capacity): self.capacity = capacity - self.ht_arr = [None] * self.capacity - + self.storage = [None] * self.capacity + self.load = 0 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. - - Implement this. - """ - return len(self.ht_arr) + # ? 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.) + return len(self.storage) def get_load_factor(self): - """ - Return the load factor for this hash table. - TODO: Skip - Implement this. - """ - # Your code here - + # ? Return the load factor for this hash table. + return self.load / self.capacity def fnv1(self, key): - """ - FNV-1 Hash, 64-bit - - Implement this, and/or DJB2. - """ - - # Your code here + # ? FNV-1 Hash, 64-bit + # ? Implement this, and/or DJB2. + pass # * Visualization of djb2: # * http://pythontutor.com/visualize.html#code=def%20djb2%28key%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20DJB2%20hash,%2032-bit%0A%0A%20%20%20%20%20%20%20%20Implement%20this,%20and/or%20FNV-1.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20hash%20%3D%205381%0A%20%20%20%20%20%20%20%20for%20x%20in%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20hash%20%3D%20%28%28hash%20%3C%3C%205%29%20%2B%20hash%29%20%2B%20ord%28x%29%0A%20%20%20%20%20%20%20%20return%20hash%20%26%200xFFFFFFFF%0A%20%20%20%20%20%20%20%20%0Adjb2%28%22hello%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false def djb2(self, key): - """ - DJB2 hash, 32-bit - - Implement this, and/or FNV-1. - """ - hash = 5381 - for x in key: + # ? DJB2 hash, 32-bit + # ? Implement this, and/or FNV-1. + hash = 5381 # ? The hash value, for this hashing algoritm it will always start as 5381 + for x in key: # ? For every letter in 'key' hash = ((hash << 5) + hash) + ord(x) + # ? For each letter, hash gets set to + # ? () return hash & 0xFFFFFFFF - def hash_index(self, key): - """ - Take an arbitrary key and return a valid integer index - between within the storage capacity of the hash table. - """ - #return self.fnv1(key) % self.capacity + # ? Take an arbitrary key and return a valid integer index + # ? between within the storage capacity of the hash table. + + # ! 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. + # ? Store the value with the given key. + # ? Hash collisions should be handled with Linked List Chaining. + + idx = self.hash_index(key) + node = self.storage[idx] + new_node = HashTableEntry(key, value) + + if self.storage[idx] != None: + + if node.key == key: + node.value = value + else: + while node.next is not None: + if node.next.key == key: + node.next.value = value + break + else: + node = node.next + + node.next = new_node + else: + self.storage[idx] = new_node - Implement this. - """ - # ? Put - # ? 1. Hash our string/key, get out a number - # ? 2. Take this number and modulo it by the length of the array - # ? 3. This new number can be used as an index, so put the value at that index in our array - - self.ht_arr[self.hash_index(key)] = value + self.load += 1 def delete(self, key): - """ - Remove the value stored with the given key. - - Print a warning if the key is not found. + # ? Remove the value stored with the given key. + # ? Print a warning if the key is not found. - Implement this. - """ # ? Delete: find the value, then set to None - if self.ht_arr[self.hash_index(key)] is None: + idx = self.hash_index(key) + node = self.storage[idx] + + if node is None: print(f"The key '{key}' does not exist.'") + elif node.key == key: + node.key = None + self.load -= 1 else: - self.ht_arr[self.hash_index(key)] = None + prev_node = node + curr_node = prev_node.next + + while curr_node is not None: + if curr_node.key == key: + curr_node.key = None + break + else: + prev_node = curr_node + curr_node = curr_node.next + self.load -= 1 + + # idx = self.hash_index(key) + + # if self.storage[idx] is None: + # print(f"The key '{key}' does not exist.'") + # elif self.storage[idx].next is None: + # self.storage[idx] = None + # self.load -= 1 + # else: + # prev_node = self.storage[idx] + # curr_node = prev_node.next + + # while curr_node is not None: + # if curr_node.key == key: + # prev_node.next = curr_node.next + # else: + # prev_node = curr_node + # curr_node = curr_node.next + # self.load -= 1 def get(self, key): - """ - Retrieve the value stored with the given key. + # ? Retrieve the value stored with the given key. + # ? Returns None if the key is not found. - Returns None if the key is not found. - - Implement this. - """ # ? Get # ? 1. Hash our string/key, string --> number # ? 2. Mod this number by length of array # ? 3. Use this modded number / index to get the value there - - if self.ht_arr[self.hash_index(key)] is None: - print(f"The key '{key}' does not exist.'") - else: - return self.ht_arr[self.hash_index(key)] + idx = self.hash_index(key) + node = self.storage[idx] - def resize(self, new_capacity): - """ - Changes the capacity of the hash table and - rehashes all key/value pairs. - TODO: Skip - Implement this. - """ - # Your code here - + if node is None: + print(f"The key '{key}' does not exist.'") + else: + while node is not None: + # check for the target value + if node.key == key: + print(f" | {node.key}, {node.value} | ") + return node.value + # move to next node + else: + node = node.next + + def resize(self, new_capacity=None): + # ? Changes the capacity of the hash table and + # ? rehashes all key/value pairs. + + if self.get_load_factor() > 0.7: + if new_capacity is None: + new_capacity = self.capacity * 2 + + old_storage = self.storage + self.storage = [None] * new_capacity + + for node in old_storage: + if node is not None: + self.put(node.key, node.value) + + curr_node = node + while curr_node.next is not None: + self.put(curr_node.next.key, curr_node.next.value) + curr_node = curr_node.next if __name__ == "__main__": From 50f1a6449b91c69c0c06032b4d8637b621c9d772 Mon Sep 17 00:00:00 2001 From: Ken Diehl Date: Wed, 4 Nov 2020 21:43:42 -0500 Subject: [PATCH 3/6] Completed lookup_table.py --- applications/lookup_table/lookup_table.py | 18 +++++++++++++++--- hashtable/hashtable.py | 18 ++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/applications/lookup_table/lookup_table.py b/applications/lookup_table/lookup_table.py index 05b7d37fa..bb22dab29 100644 --- a/applications/lookup_table/lookup_table.py +++ b/applications/lookup_table/lookup_table.py @@ -1,5 +1,7 @@ -# Your code here +import math +import random +fun = {} def slowfun_too_slow(x, y): v = math.pow(x, y) @@ -14,8 +16,18 @@ 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 - + duo = (x, y) + + if duo in fun: + return fun[duo] + else: + v = math.pow(x, y) + v = math.factorial(v) + v //= (x + y) + v %= 982451653 + + fun[duo] = v + return v # Do not modify below this line! diff --git a/hashtable/hashtable.py b/hashtable/hashtable.py index 3d272702f..66c5aa8ad 100644 --- a/hashtable/hashtable.py +++ b/hashtable/hashtable.py @@ -88,6 +88,7 @@ def put(self, key, value): self.storage[idx] = new_node self.load += 1 + def delete(self, key): # ? Remove the value stored with the given key. @@ -177,6 +178,23 @@ def resize(self, new_capacity=None): while curr_node.next is not None: self.put(curr_node.next.key, curr_node.next.value) curr_node = curr_node.next + elif self.get_load_factor() < 0.2: + if new_capacity is None and self.capacity / 2 >= 8: + new_capacity = self.capacity / 2 + else: + new_capacity = 8 + + old_storage = self.storage + self.storage = [None] * new_capacity + + for node in old_storage: + if node is not None: + self.put(node.key, node.value) + + curr_node = node + while curr_node.next is not None: + self.put(curr_node.next.key, curr_node.next.value) + curr_node = curr_node.next if __name__ == "__main__": From 1706ba694fb844b8c32731c180bf1dd9660c3f9a Mon Sep 17 00:00:00 2001 From: Ken Diehl Date: Wed, 4 Nov 2020 23:23:57 -0500 Subject: [PATCH 4/6] Completed word_count.py --- applications/crack_caesar/crack_caesar.py | 48 ++++++++++++++++++++- applications/expensive_seq/expensive_seq.py | 8 +++- applications/word_count/word_count.py | 14 +++++- 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/applications/crack_caesar/crack_caesar.py b/applications/crack_caesar/crack_caesar.py index 1418f0ef3..abf417afa 100644 --- a/applications/crack_caesar/crack_caesar.py +++ b/applications/crack_caesar/crack_caesar.py @@ -1,5 +1,51 @@ # Use frequency analysis to find the key to ciphertext.txt, and then # decode it. -# Your code here +# master_frequency = { +# "E": 11.53, +# "T": 9.75, +# "A": 8.46, +# "O": 8.08, +# "H": 7.71, +# "N": 6.73, +# "R": 6.29, +# "I": 5.84, +# "S": 5.56, +# "D": 4.74, +# "L": 3.92, +# "W": 3.08, +# "U": 2.59, +# "G": 2.48, +# "F": 2.42, +# "B": 2.19, +# "M": 2.18, +# "Y": 2.02, +# "C": 1.58, +# "P": 1.08, +# "K": 0.84, +# "V": 0.59, +# "Q": 0.17, +# "J": 0.07, +# "X": 0.07, +# "Z": 0.03, +# } +aaaaaaa = open("ciphertext.txt", 'r') +for line in aaaaaaa.readlines(1): + print(str(line)) +# for line in cipher: +# print(line) +# cipher.close() +# ? Import text +# ? make it a list + +# ? Init a dict of count and a-z keys with vals of 0 + +# ? loop through text +# ? if letter, increase its count in dict by 1 and val of 'count' by one + +# ? Then go through alphabet dict and divide letter count by length count and reassign it + +# ? loop again and swap frequency value for corresponding letter + +# ? Loop through text and swap each letter diff --git a/applications/expensive_seq/expensive_seq.py b/applications/expensive_seq/expensive_seq.py index 5c82b8453..273d66529 100644 --- a/applications/expensive_seq/expensive_seq.py +++ b/applications/expensive_seq/expensive_seq.py @@ -1,8 +1,14 @@ # Your code here - +stupid_numbers = {} def expensive_seq(x, y, z): # Your code here + # trip = (x, y, z) + print(x, y, z) + if x <= 0: + return y + z + if x > 0: + return expensive_seq(x-1,y+1,z) + expensive_seq(x-2,y+2,z*2) + expensive_seq(x-3,y+3,z*3) diff --git a/applications/word_count/word_count.py b/applications/word_count/word_count.py index a20546425..7e5b69345 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 +def word_count(s): + frequency = {} + ignore = ['"', ':', ';', ',', '.', '-', '+', '=', '/', '\\', '|', '[', ']', '{', '}', '(', ')', '*', '^', '&'] + + clean_string = "".join(ch for ch in s if ch not in ignore) + for word in clean_string.lower().split(): + if word not in frequency: + frequency[word] = 1 + else: + frequency[word] += 1 + + return frequency if __name__ == "__main__": From 52610830a180b73d55ef84c65d83c7d40d0145ee Mon Sep 17 00:00:00 2001 From: Ken Diehl Date: Thu, 5 Nov 2020 21:42:57 -0500 Subject: [PATCH 5/6] Completed crach_caesar.py --- applications/crack_caesar/crack_caesar.py | 131 ++++++++++++++++------ applications/histo/histo.py | 4 + 2 files changed, 100 insertions(+), 35 deletions(-) diff --git a/applications/crack_caesar/crack_caesar.py b/applications/crack_caesar/crack_caesar.py index abf417afa..e9a512a27 100644 --- a/applications/crack_caesar/crack_caesar.py +++ b/applications/crack_caesar/crack_caesar.py @@ -1,45 +1,105 @@ # Use frequency analysis to find the key to ciphertext.txt, and then # decode it. +import math + +master_frequency = { + "E": 11.53, + "T": 9.75, + "A": 8.46, + "O": 8.08, + "H": 7.71, + "N": 6.73, + "R": 6.29, + "I": 5.84, + "S": 5.56, + "D": 4.74, + "L": 3.92, + "W": 3.08, + "U": 2.59, + "G": 2.48, + "F": 2.42, + "B": 2.19, + "M": 2.18, + "Y": 2.02, + "C": 1.58, + "P": 1.08, + "K": 0.84, + "V": 0.59, + "Q": 0.17, + "J": 0.07, + "X": 0.07, + "Z": 0.03, +} + +frequency = { + "count": 0, + "E": ["", 0], + "T": ["", 0], + "A": ["", 0], + "O": ["", 0], + "H": ["", 0], + "N": ["", 0], + "R": ["", 0], + "I": ["", 0], + "S": ["", 0], + "D": ["", 0], + "L": ["", 0], + "W": ["", 0], + "U": ["", 0], + "G": ["", 0], + "F": ["", 0], + "B": ["", 0], + "M": ["", 0], + "Y": ["", 0], + "C": ["", 0], + "P": ["", 0], + "K": ["", 0], + "V": ["", 0], + "Q": ["", 0], + "J": ["", 0], + "X": ["", 0], + "Z": ["", 0], +} + +# ? Open the cipher and create file for decrypted text +theFile = open("ciphertext.txt", "r") +decrypted = open("decrypted.txt", "w") + +# ? Count each letter in the cipher +for ch in theFile.read(): + if ch in frequency: + frequency[ch][1] += 1 + frequency["count"] += 1 + +# ? Calculate the frequency of each letter and check if its close to master frequency +for ch in frequency: + if ch != "count": + frequency[ch][1] = round((frequency[ch][1] / frequency["count"]) * 100, 2) + + for mch in master_frequency: + if math.isclose(frequency[ch][1], master_frequency[mch]): + frequency[ch][0] = mch + +# ? Reset the pointer of cipher file to beginning +theFile.seek(0) + +# ? Write the decrypted file +for ch in theFile.read(): + if ch in frequency: + decrypted.write(frequency[ch][0]) + else: + decrypted.write(ch) + +theFile.close() +decrypted.close() + -# master_frequency = { -# "E": 11.53, -# "T": 9.75, -# "A": 8.46, -# "O": 8.08, -# "H": 7.71, -# "N": 6.73, -# "R": 6.29, -# "I": 5.84, -# "S": 5.56, -# "D": 4.74, -# "L": 3.92, -# "W": 3.08, -# "U": 2.59, -# "G": 2.48, -# "F": 2.42, -# "B": 2.19, -# "M": 2.18, -# "Y": 2.02, -# "C": 1.58, -# "P": 1.08, -# "K": 0.84, -# "V": 0.59, -# "Q": 0.17, -# "J": 0.07, -# "X": 0.07, -# "Z": 0.03, -# } - -aaaaaaa = open("ciphertext.txt", 'r') -for line in aaaaaaa.readlines(1): - print(str(line)) -# for line in cipher: -# print(line) -# cipher.close() # ? Import text # ? make it a list # ? Init a dict of count and a-z keys with vals of 0 + # * for cd in range(ord('A'), ord('Z') + 1): + # * frequency[chr(cd)] = 0 # ? loop through text # ? if letter, increase its count in dict by 1 and val of 'count' by one @@ -49,3 +109,4 @@ # ? loop again and swap frequency value for corresponding letter # ? Loop through text and swap each letter + diff --git a/applications/histo/histo.py b/applications/histo/histo.py index 6014a8e13..4df884448 100644 --- a/applications/histo/histo.py +++ b/applications/histo/histo.py @@ -1,2 +1,6 @@ # Your code here +theFile = open("robin.txt", "r") +for line in theFile.readlines(): + print(line) +theFile.close() \ No newline at end of file From 05cdd58ccb8df31e60795b0a6750596826b48855 Mon Sep 17 00:00:00 2001 From: Ken Diehl Date: Fri, 6 Nov 2020 10:25:32 -0500 Subject: [PATCH 6/6] Small changes, moving workstations --- applications/histo/histo.py | 42 +++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/applications/histo/histo.py b/applications/histo/histo.py index 4df884448..3778afb03 100644 --- a/applications/histo/histo.py +++ b/applications/histo/histo.py @@ -1,6 +1,44 @@ # Your code here +histo = {} +longest = 0 +ignore = ['"', ':', ';', ',', '.', '-', '+', '=', '/', '\\', '|', '[', ']', '{', '}', '(', ')', '*', '^', '&', '?', '!'] theFile = open("robin.txt", "r") + for line in theFile.readlines(): - print(line) -theFile.close() \ No newline at end of file + clean_line = "".join(ch for ch in line if ch not in ignore).lower() + for word in clean_line.split(): + if word not in histo: + histo[word] = "#" + if len(word) > longest: + longest = len(word) + else: + histo[word] = histo[word] + "#" + +sorted_histo = sorted(histo.items(), key=lambda x: x[0], reverse=False) +# sorted_histo_o = sorted(sorted_.items(), key=lambda x: x[0], reverse=False) + + +# for word in histo: +# just = longest + 2 +# print(f"{word.ljust(just)} {histo[word]}") + +print(sorted_histo) +# print(histo) + + +# def word_count(s): +# frequency = {} +# ignore = ['"', ':', ';', ',', '.', '-', '+', '=', '/', '\\', '|', '[', ']', '{', '}', '(', ')', '*', '^', '&'] + +# clean_string = "".join(ch for ch in s if ch not in ignore) +# for word in clean_string.lower().split(): +# if word not in frequency: +# frequency[word] = 1 +# else: +# frequency[word] += 1 + +# return frequency + +theFile.close() +