diff --git a/applications/crack_caesar/crack_caesar.py b/applications/crack_caesar/crack_caesar.py index 1418f0ef3..e9a512a27 100644 --- a/applications/crack_caesar/crack_caesar.py +++ b/applications/crack_caesar/crack_caesar.py @@ -1,5 +1,112 @@ # Use frequency analysis to find the key to ciphertext.txt, and then # decode it. +import math -# 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, +} + +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() + + +# ? 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 + +# ? 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/histo/histo.py b/applications/histo/histo.py index 6014a8e13..3778afb03 100644 --- a/applications/histo/histo.py +++ b/applications/histo/histo.py @@ -1,2 +1,44 @@ # Your code here +histo = {} +longest = 0 +ignore = ['"', ':', ';', ',', '.', '-', '+', '=', '/', '\\', '|', '[', ']', '{', '}', '(', ')', '*', '^', '&', '?', '!'] + +theFile = open("robin.txt", "r") + +for line in theFile.readlines(): + 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() 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/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__": diff --git a/hashtable/hashtable.py b/hashtable/hashtable.py index 0205f0ba9..66c5aa8ad 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,110 +12,189 @@ 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): - # Your code here - + self.capacity = 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. - """ - # Your code here + # ? 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. - - 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. - """ - # Your code here - + # ? 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. + # ? Store the value with the given key. + # ? Hash collisions should be handled with Linked List Chaining. - Hash collisions should be handled with Linked List Chaining. + idx = self.hash_index(key) + node = self.storage[idx] + new_node = HashTableEntry(key, value) - Implement this. - """ - # Your code here + 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 - def delete(self, key): - """ - Remove the value stored with the given key. - - Print a warning if the key is not found. + node.next = new_node + else: + self.storage[idx] = new_node - Implement this. - """ - # Your code here + self.load += 1 + + def delete(self, key): + # ? Remove the value stored with the given key. + # ? Print a warning if the key is not found. + + # ? Delete: find the value, then set to 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: + 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. - - Returns None if the key is not found. - - Implement this. - """ - # Your code here - - - def resize(self, new_capacity): - """ - Changes the capacity of the hash table and - rehashes all key/value pairs. - - Implement this. - """ - # Your code here - + # ? Retrieve the value stored with the given key. + # ? Returns None if the key is not found. + + # ? 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 + + idx = self.hash_index(key) + node = self.storage[idx] + + 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 + 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__":