From d98f46ce647846b0aa30b2e16a30fd4e152a1bf5 Mon Sep 17 00:00:00 2001 From: Carlos Maiolino Date: Thu, 10 Jul 2025 22:55:07 +0200 Subject: Add new code Signed-off-by: Carlos Maiolino --- mit/cipher/ps6.py | 271 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 mit/cipher/ps6.py (limited to 'mit/cipher/ps6.py') diff --git a/mit/cipher/ps6.py b/mit/cipher/ps6.py new file mode 100644 index 0000000..34e8fba --- /dev/null +++ b/mit/cipher/ps6.py @@ -0,0 +1,271 @@ +import string + +### DO NOT MODIFY THIS FUNCTION ### +def load_words(file_name): + ''' + 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. + + Depending on the size of the word list, this function may + take a while to finish. + ''' + print('Loading word list from file...') + # inFile: file + in_file = open(file_name, 'r') + # line: string + line = in_file.readline() + # word_list: list of strings + word_list = line.split() + print(' ', len(word_list), 'words loaded.') + in_file.close() + return word_list + +### DO NOT MODIFY THIS FUNCTION ### +def is_word(word_list, word): + ''' + Determines if word is a valid word, ignoring + capitalization and punctuation + + word_list (list): list of words in the dictionary. + word (string): a possible word. + + Returns: True if word is in word_list, False otherwise + + Example: + >>> is_word(word_list, 'bat') returns + True + >>> is_word(word_list, 'asdf') returns + False + ''' + word = word.lower() + word = word.strip(" !@#$%^&*()-_+={}[]|\:;'<>?,./\"") + return word in word_list + +### DO NOT MODIFY THIS FUNCTION ### +def get_story_string(): + """ + Returns: a joke in encrypted text. + """ + f = open("story.txt", "r") + story = str(f.read()) + f.close() + return story + +WORDLIST_FILENAME = 'words.txt' + +class Message(object): + ### DO NOT MODIFY THIS METHOD ### + def __init__(self, text): + ''' + Initializes a Message object + + text (string): the message's text + + a Message object has two attributes: + self.message_text (string, determined by input text) + self.valid_words (list, determined using helper function load_words + ''' + self.message_text = text + self.valid_words = load_words(WORDLIST_FILENAME) + + ### DO NOT MODIFY THIS METHOD ### + def get_message_text(self): + ''' + Used to safely access self.message_text outside of the class + + Returns: self.message_text + ''' + return self.message_text + + ### DO NOT MODIFY THIS METHOD ### + def get_valid_words(self): + ''' + Used to safely access a copy of self.valid_words outside of the class + + Returns: a COPY of self.valid_words + ''' + return self.valid_words[:] + + def build_shift_dict(self, shift): + ''' + Creates a dictionary that can be used to apply a cipher to a letter. + 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. + + 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). + ''' + cipher = {} + lower_alpha = string.ascii_lowercase + upper_alpha = string.ascii_uppercase + + # Both strings have the same length, and same shift, so, loop through + # both together, to avoid two loops + + for i in range(len(lower_alpha)): + new_idx = lower_alpha.index(lower_alpha[i]) + shift + + if ((len(lower_alpha)) - new_idx) <= 0: + new_idx = abs((len(lower_alpha)) - new_idx) + + cipher[lower_alpha[i]] = lower_alpha[new_idx] + cipher[upper_alpha[i]] = upper_alpha[new_idx] + + return cipher + + 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 + + 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 + ''' + cipher = self.build_shift_dict(shift) + + new_msg = '' + + for i in self.get_message_text(): + + if i in cipher.keys(): + new_msg += cipher[i] + else: + new_msg += i + + return new_msg +class PlaintextMessage(Message): + def __init__(self, text, shift): + ''' + Initializes a PlaintextMessage object + + text (string): the message's text + shift (integer): the shift associated with this message + + A PlaintextMessage object inherits from Message and has five attributes: + self.message_text (string, determined by input text) + self.valid_words (list, determined using helper function load_words) + self.shift (integer, determined by input shift) + self.encrypting_dict (dictionary, built using shift) + self.message_text_encrypted (string, created using shift) + + Hint: consider using the parent class constructor so less + code is repeated + ''' + Message.__init__(self, text) + self.shift = shift + self.encrypting_dict = self.build_shift_dict(shift) + self.message_text_encrypted = self.apply_shift(shift) + + def get_shift(self): + ''' + Used to safely access self.shift outside of the class + + Returns: self.shift + ''' + return self.shift + + def get_encrypting_dict(self): + ''' + Used to safely access a copy self.encrypting_dict outside of the class + + Returns: a COPY of self.encrypting_dict + ''' + return self.encrypting_dict.copy() + + def get_message_text_encrypted(self): + ''' + Used to safely access self.message_text_encrypted outside of the class + + Returns: self.message_text_encrypted + ''' + return self.message_text_encrypted + + def change_shift(self, shift): + ''' + Changes self.shift of the PlaintextMessage and updates other + attributes determined by shift (ie. self.encrypting_dict and + message_text_encrypted). + + shift (integer): the new shift that should be associated with this message. + 0 <= shift < 26 + + Returns: nothing + ''' + PlaintextMessage.__init__(self, self.get_message_text(), shift) + +class CiphertextMessage(Message): + def __init__(self, text): + ''' + Initializes a CiphertextMessage object + + text (string): the message's text + + a CiphertextMessage object has two attributes: + self.message_text (string, determined by input text) + self.valid_words (list, determined using helper function load_words) + ''' + Message.__init__(self, text) + + def decrypt_message(self): + ''' + Decrypt self.message_text by trying every possible shift value + and find the "best" one. We will define "best" as the shift that + creates the maximum number of real words when we use apply_shift(shift) + on the message text. If s is the original shift value used to encrypt + the message, then we would expect 26 - s to be the best shift value + for decrypting it. + + Note: if multiple shifts are equally good such that they all create + the maximum number of you may choose any of those shifts (and their + corresponding decrypted messages) to return + + Returns: a tuple of the best shift value used to decrypt the message + and the decrypted message text using that shift value + ''' + + best_shift = 0 + best_count = 0 + + for i in range(0, 26): + + cur_text = self.apply_shift(i) + wlist = cur_text.split(' ') + + count = 0 + for w in wlist: + + if is_word(self.valid_words, w): + count += 1 + + if count >= best_count: + best_count = count + best_shift = i + + return (best_shift, self.apply_shift(best_shift)) +#Example test case (PlaintextMessage) +plaintext = PlaintextMessage('hello', 2) +print('Expected Output: jgnnq') +print('Actual Output:', plaintext.get_message_text_encrypted()) + +#Example test case (CiphertextMessage) +ciphertext = CiphertextMessage('jgnnq') +print('Expected Output:', (24, 'hello')) +print('Actual Output:', ciphertext.decrypt_message()) + + + +def decrypt_story(): + cipherMsg = CiphertextMessage(get_story_string()) + + return cipherMsg.decrypt_message() -- cgit v1.2.3