diff --git a/exercise/ps4b.py b/exercise/ps4b.py index 4e5e101..a7ebe0d 100644 --- a/exercise/ps4b.py +++ b/exercise/ps4b.py @@ -1,10 +1,10 @@ # Problem Set 4B -# Name: +# Name: Mai Thu Trang # Collaborators: # Time Spent: x:xx import string - +import os ### HELPER CODE ### def load_words(file_name): ''' @@ -18,7 +18,7 @@ def load_words(file_name): ''' print("Loading word list from file...") # inFile: file - inFile = open(file_name, 'r') + inFile = open(os.path.join(os.path.dirname(__file__), file_name), 'r') # fill in the file name # wordlist: list of strings wordlist = [] for line in inFile: @@ -50,7 +50,7 @@ def get_story_string(): """ Returns: a story in encrypted text. """ - f = open("story.txt", "r") + f = open(os.path.join(os.path.dirname(__file__), "story.txt"), "r") story = str(f.read()) f.close() return story @@ -70,7 +70,8 @@ def __init__(self, text): self.message_text (string, determined by input text) self.valid_words (list, determined using helper function load_words) ''' - pass #delete this line and replace with your code here + self.message_text = text + self.valid_words = load_words(WORDLIST_FILENAME) def get_message_text(self): ''' @@ -78,7 +79,7 @@ def get_message_text(self): Returns: self.message_text ''' - pass #delete this line and replace with your code here + return self.message_text def get_valid_words(self): ''' @@ -87,7 +88,7 @@ def get_valid_words(self): Returns: a COPY of self.valid_words ''' - pass #delete this line and replace with your code here + return self.valid_words[:] def build_shift_dict(self, shift): ''' @@ -95,34 +96,48 @@ def build_shift_dict(self, shift): The dictionary maps every uppercase and lowercase letter to a character shifted down the alphabet by the input shift. The dictionary should have 52 keys of all the uppercase letters and all the lowercase - letters only. + letters only. - shift (integer): the amount by which to shift every letter of the + shift (integer): the amount by which to shift every letter of the alphabet. 0 <= shift < 26 - Returns: a dictionary mapping a letter (string) to - another letter (string). + Returns: a dictionary mapping a letter (string) to + another letter (string). ''' - pass #delete this line and replace with your code here - + shift = shift % 26 + mapping = {} + lower = string.ascii_lowercase + upper = string.ascii_uppercase + for i in range(len(lower)): + mapping[lower[i]] = lower[(i + shift) % 26] + mapping[upper[i]] = upper[(i + shift) % 26] + return mapping + def apply_shift(self, shift): ''' Applies the Caesar Cipher to self.message_text with the input shift. Creates a new string that is self.message_text shifted down the - alphabet by some number of characters determined by the input shift + alphabet by some number of characters determined by the input shift shift (integer): the shift with which to encrypt the message. 0 <= shift < 26 Returns: the message text (string) in which every character is shifted - down the alphabet by the input shift + down the alphabet by the input shift ''' - pass #delete this line and replace with your code here + shift_dict = self.build_shift_dict(shift) + result = '' + for char in self.message_text: + if char in shift_dict: + result += shift_dict[char] + else: + result += char + return result class PlaintextMessage(Message): def __init__(self, text, shift): ''' - Initializes a PlaintextMessage object + Initializes a PlaintextMessage object text (string): the message's text shift (integer): the shift associated with this message @@ -135,7 +150,10 @@ def __init__(self, text, shift): self.message_text_encrypted (string, created using shift) ''' - pass #delete this line and replace with your code here + super().__init__(text) + self.shift = shift + self.encryption_dict = self.build_shift_dict(shift) + self.message_text_encrypted = self.apply_shift(shift) def get_shift(self): ''' @@ -143,7 +161,7 @@ def get_shift(self): Returns: self.shift ''' - pass #delete this line and replace with your code here + return self.shift def get_encryption_dict(self): ''' @@ -151,7 +169,7 @@ def get_encryption_dict(self): Returns: a COPY of self.encryption_dict ''' - pass #delete this line and replace with your code here + return self.encryption_dict.copy() def get_message_text_encrypted(self): ''' @@ -159,7 +177,7 @@ def get_message_text_encrypted(self): Returns: self.message_text_encrypted ''' - pass #delete this line and replace with your code here + return self.message_text_encrypted def change_shift(self, shift): ''' @@ -171,7 +189,9 @@ def change_shift(self, shift): Returns: nothing ''' - pass #delete this line and replace with your code here + self.shift = shift + self.encryption_dict = self.build_shift_dict(shift) + self.message_text_encrypted = self.apply_shift(shift) class CiphertextMessage(Message): @@ -185,7 +205,7 @@ def __init__(self, text): self.message_text (string, determined by input text) self.valid_words (list, determined using helper function load_words) ''' - pass #delete this line and replace with your code here + super().__init__(text) def decrypt_message(self): ''' @@ -203,7 +223,19 @@ def decrypt_message(self): Returns: a tuple of the best shift value used to decrypt the message and the decrypted message text using that shift value ''' - pass #delete this line and replace with your code here + max_valid_words = 0 + best_shift = 0 + decrypted_message = '' + for shift in range(26): + decrypted_text = self.apply_shift(26 - shift) + words = decrypted_text.split(' ') + valid_word_count = sum(1 for word in words if is_word(self.valid_words, word)) + if valid_word_count > max_valid_words: + max_valid_words = valid_word_count + best_shift = 26 - shift + decrypted_message = decrypted_text + + return (best_shift, decrypted_message) if __name__ == '__main__': @@ -217,8 +249,10 @@ def decrypt_message(self): # print('Expected Output:', (24, 'hello')) # print('Actual Output:', ciphertext.decrypt_message()) - #TODO: WRITE YOUR TEST CASES HERE - - #TODO: best shift value and unencrypted story + plaintext = PlaintextMessage('hello', 2) + print('Expected Output: jgnnq') + print('Actual Output:', plaintext.get_message_text_encrypted()) - pass #delete this line and replace with your code here + ciphertext = CiphertextMessage('jgnnq') + print('Expected Output:', (24, 'hello')) + print('Actual Output:', ciphertext.decrypt_message()) \ No newline at end of file diff --git a/exercise/ps4c.py b/exercise/ps4c.py index 1ceb239..851b546 100644 --- a/exercise/ps4c.py +++ b/exercise/ps4c.py @@ -4,13 +4,13 @@ # Time Spent: x:xx import string -from ps4a import get_permutations +import os ### HELPER CODE ### def load_words(file_name): ''' - file_name (string): the name of the file containing - the list of words to load + file_name (string): the name of the file containing + the list of words to load Returns: a list of valid words. Words are strings of lowercase letters. @@ -20,7 +20,8 @@ def load_words(file_name): print("Loading word list from file...") # inFile: file - inFile = open(file_name, 'r') + inPath = os.path.join(os.path.dirname(__file__), file_name) + inFile = open(inPath, 'r') # wordlist: list of strings wordlist = [] for line in inFile: @@ -70,7 +71,8 @@ def __init__(self, text): self.message_text (string, determined by input text) self.valid_words (list, determined using helper function load_words) ''' - pass #delete this line and replace with your code here + self.message_text = text + self.valid_words = load_words(WORDLIST_FILENAME) def get_message_text(self): ''' @@ -78,7 +80,7 @@ def get_message_text(self): Returns: self.message_text ''' - pass #delete this line and replace with your code here + return self.message_text def get_valid_words(self): ''' @@ -87,39 +89,54 @@ def get_valid_words(self): Returns: a COPY of self.valid_words ''' - pass #delete this line and replace with your code here - + return self.valid_words.copy() + + def build_transpose_dict(self, vowels_permutation): ''' vowels_permutation (string): a string containing a permutation of vowels (a, e, i, o, u) Creates a dictionary that can be used to apply a cipher to a letter. The dictionary maps every uppercase and lowercase letter to an - uppercase and lowercase letter, respectively. Vowels are shuffled - according to vowels_permutation. The first letter in vowels_permutation + uppercase and lowercase letter, respectively. Vowels are shuffled + according to vowels_permutation. The first letter in vowels_permutation corresponds to a, the second to e, and so on in the order a, e, i, o, u. - The consonants remain the same. The dictionary should have 52 + The consonants remain the same. The dictionary should have 52 keys of all the uppercase letters and all the lowercase letters. Example: When input "eaiuo": Mapping is a->e, e->a, i->i, o->u, u->o and "Hello World!" maps to "Hallu Wurld!" - Returns: a dictionary mapping a letter (string) to - another letter (string). + Returns: a dictionary mapping a letter (string) to + another letter (string). ''' - - pass #delete this line and replace with your code here - + mapping = {} + for i, vowel in enumerate(VOWELS_LOWER): + mapping[vowel] = vowels_permutation[i] + for i, vowel in enumerate(VOWELS_UPPER): + mapping[vowel] = vowels_permutation[i].upper() + for consonant in CONSONANTS_LOWER: + mapping[consonant] = consonant + for consonant in CONSONANTS_UPPER: + mapping[consonant] = consonant + return mapping + def apply_transpose(self, transpose_dict): ''' transpose_dict (dict): a transpose dictionary - Returns: an encrypted version of the message text, based + Returns: an encrypted version of the message text, based on the dictionary ''' - - pass #delete this line and replace with your code here + self.transpose_dict = transpose_dict + encrypted_message = '' + for char in self.message_text: + if char in transpose_dict: + encrypted_message += transpose_dict[char] + else: + encrypted_message += char + return encrypted_message class EncryptedSubMessage(SubMessage): def __init__(self, text): @@ -132,27 +149,39 @@ def __init__(self, text): self.message_text (string, determined by input text) self.valid_words (list, determined using helper function load_words) ''' - pass #delete this line and replace with your code here + super().__init__(text) def decrypt_message(self): ''' - Attempt to decrypt the encrypted message + Attempt to decrypt the encrypted message Idea is to go through each permutation of the vowels and test it on the encrypted message. For each permutation, check how many words in the decrypted text are valid English words, and return the decrypted message with the most English words. - If no good permutations are found (i.e. no permutations result in + If no good permutations are found (i.e. no permutations result in at least 1 valid word), return the original string. If there are multiple permutations that yield the maximum number of words, return any one of them. - Returns: the best decrypted message + Returns: the best decrypted message Hint: use your function from Part 4A ''' - pass #delete this line and replace with your code here + from itertools import permutations + vowel_permutations = [''.join(p) for p in permutations(VOWELS_LOWER)] + max_valid_words = 0 + best_decrypted_message = self.message_text + for perm in vowel_permutations: + transpose_dict = self.build_transpose_dict(perm) + decrypted_message = self.apply_transpose(transpose_dict) + words = decrypted_message.split(' ') + valid_word_count = sum(1 for word in words if is_word(self.get_valid_words(), word)) + if valid_word_count > max_valid_words: + max_valid_words = valid_word_count + best_decrypted_message = decrypted_message + return best_decrypted_message if __name__ == '__main__': @@ -166,5 +195,5 @@ def decrypt_message(self): print("Actual encryption:", message.apply_transpose(enc_dict)) enc_message = EncryptedSubMessage(message.apply_transpose(enc_dict)) print("Decrypted message:", enc_message.decrypt_message()) - + #TODO: WRITE YOUR TEST CASES HERE