diff --git a/applications/crack_caesar/crack_caesar.py b/applications/crack_caesar/crack_caesar.py index 1418f0ef3..1b5fab885 100644 --- a/applications/crack_caesar/crack_caesar.py +++ b/applications/crack_caesar/crack_caesar.py @@ -1,5 +1,40 @@ -# Use frequency analysis to find the key to ciphertext.txt, and then -# decode it. +import os -# Your code here +input_text = os.path.join(os.path.dirname(__file__), 'cyphertext.txt') +# Read in all the words in one go +with open(input_text) as f: + words = f.read() + +lowercase_words = words.lower() +encrypted_dict = {} + +# Get frequency of characters in encryption +for char in lowercase_words: + if char.isalpha() != True: + continue + if char in encrypted_dict: + encrypted_dict[char] += 1 + else: + encrypted_dict[char] = 1 + +# Sort characters by frequency +sorted_encrypted_dict = {k: v for k, v in sorted(encrypted_dict.items(), key=lambda item: item[1], reverse=True)} + +# Use sorted encryption dictionary to replace characters +encryption_key = ['e', 't', 'a', 'o', 'h', 'n', 'r', 'i', 's', 'd', 'l', 'w', 'u', 'g', 'f', 'b', 'm', 'y', 'c', 'p', 'k', 'v', 'q', 'j', 'x', 'z'] +transfer_dict = {} +result = "" + +# Create a tranfer Dictionary +for index, key in enumerate(sorted_encrypted_dict): + transfer_dict[key] = encryption_key[index] + +# Use Transfer dictionary to replace characters in CypherText.txt +for char in lowercase_words: + if char.isalpha() != True: + result += char + if char in transfer_dict: + result += transfer_dict[char] + +print(result) diff --git a/applications/expensive_seq/expensive_seq.py b/applications/expensive_seq/expensive_seq.py index 5c82b8453..866d437c5 100644 --- a/applications/expensive_seq/expensive_seq.py +++ b/applications/expensive_seq/expensive_seq.py @@ -1,9 +1,18 @@ # Your code here - +dictionary = {} def expensive_seq(x, y, z): - # Your code here + if (x, y, z) in dictionary: + return dictionary[(x, y, z)] + + result = 0 + if x <= 0: + result = y + z + if x > 0: + result = expensive_seq(x-1,y+1,z) + expensive_seq(x-2,y+2,z*2) + expensive_seq(x-3,y+3,z*3) + dictionary[(x, y, z)] = result + return result if __name__ == "__main__": diff --git a/applications/histo/histo.py b/applications/histo/histo.py index 6014a8e13..99bec6788 100644 --- a/applications/histo/histo.py +++ b/applications/histo/histo.py @@ -1,2 +1,28 @@ -# Your code here +import os +import collections +input_text = os.path.join(os.path.dirname(__file__), 'robin.txt') + +# Read in all the words in one go +with open(input_text) as f: + words = f.read() + +lower_case_words = words.lower() +split_words = lower_case_words.split() +dictionary = {} +largest_word = 0 + +for word in split_words: + if len(word) > largest_word: + largest_word = len(word) + if word not in dictionary: + dictionary[word] = 1 + elif word in dictionary: + dictionary[word] += 1 + +sorted_dictionary = {k: v for k, v in sorted(dictionary.items(), key=lambda item: item[1], reverse=True)} + +for key, value in sorted_dictionary.items(): + hash_number = "#" * value + space = largest_word + 2 + print(f"{key:{space}} {hash_number}") diff --git a/applications/lookup_table/lookup_table.py b/applications/lookup_table/lookup_table.py index 05b7d37fa..f68e18951 100644 --- a/applications/lookup_table/lookup_table.py +++ b/applications/lookup_table/lookup_table.py @@ -1,22 +1,30 @@ -# Your code here +import math +import random +dictionary = {} -def slowfun_too_slow(x, y): - v = math.pow(x, y) - v = math.factorial(v) - v //= (x + y) - v %= 982451653 +# def slowfun_too_slow(x, y): +# v = math.pow(x, y) +# v = math.factorial(v) +# v //= (x + y) +# v %= 982451653 - return v +# return v 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 - + if (x, y) in dictionary: + return dictionary[(x, y)] + v = math.pow(x, y) + v = math.factorial(v) + v //= (x + y) + v %= 982451653 + dictionary[(x, y)] = v + return v # Do not modify below this line! diff --git a/applications/markov/markov.py b/applications/markov/markov.py index 1d138db10..50de817ae 100644 --- a/applications/markov/markov.py +++ b/applications/markov/markov.py @@ -1,13 +1,36 @@ import random +import os.path + +input_text = os.path.join(os.path.dirname(__file__), 'input.txt') # Read in all the words in one go -with open("input.txt") as f: +with open(input_text) as f: words = f.read() +# print(words) # TODO: analyze which words can follow other words -# Your code here +split_words = words.split() +word_dictionary = {} + +for index, word in enumerate(split_words): + if word not in word_dictionary and index != len(split_words) - 1: + word_dictionary[word] = [split_words[index + 1]] + elif word in word_dictionary: + word_dictionary[word] += [split_words[index + 1]] +# print(word_dictionary) # TODO: construct 5 random sentences -# Your code here +stop_chars = [".", "?", "!"] + +for _ in range(5): + begin_word = random.choice(list(word_dictionary.keys())) + curr_word = begin_word + sentence = "" + + while curr_word[-1] not in stop_chars: + sentence += curr_word + " " + word = word_dictionary[curr_word] + curr_word = random.choice(list(word)) + print(sentence) diff --git a/applications/no_dups/no_dups.py b/applications/no_dups/no_dups.py index caa162c8c..3a551c3f2 100644 --- a/applications/no_dups/no_dups.py +++ b/applications/no_dups/no_dups.py @@ -1,6 +1,21 @@ + def no_dups(s): - # Your code here + array = [] + result_string = "" + split_string = s.split() + + if s == "": + return result_string + + for word in split_string: + if word not in array: + array.append(word) + result_string += word + " " + + if result_string.endswith(" "): + return result_string[:-1] + return result_string if __name__ == "__main__": diff --git a/applications/word_count/word_count.py b/applications/word_count/word_count.py index a20546425..dc2dabc32 100644 --- a/applications/word_count/word_count.py +++ b/applications/word_count/word_count.py @@ -1,6 +1,22 @@ def word_count(s): - # Your code here + dictionary = {} + special_characters = ['"', ":", ";", ",", ".", "-", "+", "=", "/", "|", "[", "]", "{", "}", "(", ")", "*", "^", "&", "\\"] + lower_case = s.lower() + + for i in special_characters: + lower_case = lower_case.replace(i, "") + + split_string = lower_case.split() + if split_string.count == 0: + return dictionary + + for word in split_string: + if word in dictionary: + dictionary[word] += 1 + else: + dictionary[word] = 1 + return dictionary if __name__ == "__main__": diff --git a/hashtable/hashtable.py b/hashtable/hashtable.py index 0205f0ba9..39b2b6af5 100644 --- a/hashtable/hashtable.py +++ b/hashtable/hashtable.py @@ -1,12 +1,79 @@ +class Node: + def __init__(self, value): + self.value = value + self.next = None + class HashTableEntry: - """ - Linked List hash table key/value pair - """ def __init__(self, key, value): self.key = key self.value = value - self.next = None + def __eq__(self, other): + if isinstance(other, HashTableEntry): + return self.key == other.key + return False + + +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 + + def find(self, value): + # return node with value + curr = self.head + + #walk through the LL and check the value + while curr != None: + if curr.value == value: + return curr + curr = curr.next + + return None + + def delete(self, value): + curr = self.head + + # special case for remove head + if curr.value == value: + self.head = curr.next + curr.next = None + return curr + + prev = None + + while curr != None: + if curr.value == value: + prev.next = curr.next + curr.next = None + return curr + else: + prev = curr + curr = curr.next + + return None + + # insert node at head of list + def add_to_head(self, node): + node.next = self.head + self.head = node + + # overwrite node or insert node at head + def insert_at_head_or_overwrite(self, node): + existing_node = self.find(node.value) + if existing_node != None: + existing_node.value = node.value + return False + else: + self.add_to_head(node) + return True # Hash table can't have fewer than this many slots MIN_CAPACITY = 8 @@ -21,7 +88,9 @@ class HashTable: """ def __init__(self, capacity): - # Your code here + self.capacity = capacity + self.table = [None] * capacity + self.count = 0 def get_num_slots(self): @@ -34,8 +103,8 @@ def get_num_slots(self): Implement this. """ - # Your code here - + return len(self.table) + def get_load_factor(self): """ @@ -43,77 +112,103 @@ def get_load_factor(self): Implement this. """ - # Your code here + return self.count / self.get_num_slots() 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 s in key: + hash = ((hash << 5) + hash) + ord(s) + 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 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 + # hash the key and get the index + index = self.hash_index(key) + # check if the table at index is empty + if self.table[index] == None: + # if it is -> initialize new LinkedList + ll = LinkedList() + ll.add_to_head(Node(HashTableEntry(key, value))) + self.table[index] = ll + # increase count + self.count += 1 + else: + # if table at index already has a LL, get reference to current LL + curr_ll: LinkedList = self.table[index] + # set the new Node as the head + did_add_new_node = curr_ll.insert_at_head_or_overwrite(Node(HashTableEntry(key, value))) + if did_add_new_node: + # increase count + self.count += 1 + + if self.get_load_factor() > 0.7: + self.resize(self.get_num_slots() * 2) + + 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 + index = self.hash_index(key) + if self.table[index] != None: + ll: LinkedList = self.table[index] + did_delete_node = ll.delete(HashTableEntry(key, None)) + if did_delete_node != None: + self.count -= 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): - """ - Retrieve the value stored with the given key. - - Returns None if the key is not found. + index = self.hash_index(key) + if self.table[index] != None: + ll: LinkedList = self.table[index] + node = ll.find(HashTableEntry(key, None)) + if node != None: + return node.value.value + return None - 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 + old_table = self.table + self.table = [None] * int(new_capacity) + self.count = 0 + self.capacity = new_capacity + + for element in old_table: + if element == None: + continue + curr_node: Node = element.head + while curr_node != None: + temp = curr_node.next + curr_node.next = None + index = self.hash_index(curr_node.value.key) + + if self.table[index] != None: + self.table[index].add_to_head(curr_node) + else: + ll = LinkedList() + ll.add_to_head(curr_node) + self.table[index] = ll + + curr_node = temp + self.count += 1 + + def check_and_resize(self): + if self.get_load_factor() > 0.7: + self.resize(self.capacity * 2) + elif self.get_load_factor() < 0.2: + self.resize(self.capacity / 2) @@ -139,6 +234,12 @@ def resize(self, new_capacity): for i in range(1, 13): print(ht.get(f"line_{i}")) + ht.delete("line_1") + + print("deleted") + for i in range(1, 13): + print(ht.get(f"line_{i}")) + # Test resizing old_capacity = ht.get_num_slots() ht.resize(ht.capacity * 2) diff --git a/hashtable/linkedList.py b/hashtable/linkedList.py new file mode 100644 index 000000000..49c0e1f93 --- /dev/null +++ b/hashtable/linkedList.py @@ -0,0 +1,78 @@ +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 + + def find(self, value): + # return node with value + curr = self.head + + #walk through the LL and check the value + while curr != None: + if curr.value == value: + return curr + curr = curr.next + + return None + + def delete(self, value): + curr = self.head + + # special case for remove head + if curr.value == value: + self.head = curr.next + curr.next = None + return curr + + prev = None + + while curr != None: + if curr.value == value: + prev.next = curr.next + curr.next = None + return curr + else: + prev = curr + curr = curr.next + + return None + + # insert node at head of list + def add_to_head(self, node): + node.next = self.head + self.head = node + + # overwrite node or insert node at head + def insert_at_head_or_overwrite(self, node): + existing_node = self.find(node.value) + if existing_node != None: + existing_node.value = node.value + else: + self.add_to_head(node) + + +# a = Node(1) +# b = Node(2) +# c = Node(3) + +# ll = LinkedList() + +# ll.add_to_head(c) +# ll.add_to_head(b) +# ll.add_to_head(a) +# ll.insert_at_head_or_overwrite(c) +# ll.delete(2) + +# print(ll) \ No newline at end of file diff --git a/hashtable/test_hashtable.py b/hashtable/test_hashtable.py index 12ae9e388..2ef35ad42 100644 --- a/hashtable/test_hashtable.py +++ b/hashtable/test_hashtable.py @@ -147,9 +147,9 @@ def test_hash_table_removes_correctly(self): return_value = ht.get("key-7") self.assertTrue(return_value is None) return_value = ht.get("key-8") - self.assertTrue(return_value is "val-8") + self.assertTrue(return_value == "val-8") return_value = ht.get("key-9") - self.assertTrue(return_value is "val-9") + self.assertTrue(return_value == "val-9") ht.delete("key-9") ht.delete("key-8")