Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 62 additions & 28 deletions exercise/ps4b.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Problem Set 4B
# Name: <your name here>
# Name: Mai Thu Trang
# Collaborators:
# Time Spent: x:xx

import string

import os
### HELPER CODE ###
def load_words(file_name):
'''
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -70,15 +70,16 @@ 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):
'''
Used to safely access self.message_text outside of the class

Returns: self.message_text
'''
pass #delete this line and replace with your code here
return self.message_text

def get_valid_words(self):
'''
Expand All @@ -87,42 +88,56 @@ 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):
'''
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.
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
Expand All @@ -135,31 +150,34 @@ 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):
'''
Used to safely access self.shift outside of the class

Returns: self.shift
'''
pass #delete this line and replace with your code here
return self.shift

def get_encryption_dict(self):
'''
Used to safely access a copy self.encryption_dict outside of the class

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):
'''
Used to safely access self.message_text_encrypted outside of the class

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):
'''
Expand All @@ -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):
Expand All @@ -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):
'''
Expand All @@ -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__':

Expand All @@ -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())
79 changes: 54 additions & 25 deletions exercise/ps4c.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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:
Expand Down Expand Up @@ -70,15 +71,16 @@ 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):
'''
Used to safely access self.message_text outside of the class

Returns: self.message_text
'''
pass #delete this line and replace with your code here
return self.message_text

def get_valid_words(self):
'''
Expand All @@ -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):
Expand All @@ -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__':
Expand All @@ -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