# 6.00 Problem Set 4
#
# Caesar Cipher Skeleton
#
import string
import random
WORDLIST_FILENAME = "words.txt"
# -----------------------------------
# Helper code
# (you don't need to understand this helper code)
def load_words():
"""
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
inFile = open(WORDLIST_FILENAME, 'r', 0)
# line: string
line = inFile.readline()
# wordlist: list of strings
wordlist = line.split()
print " ", len(wordlist), "words loaded."
return wordlist
wordlist = load_words()
def is_word(wordlist, word):
"""
Determines if word is a valid word.
wordlist: list of words in the dictionary.
word: a possible word.
returns True if word is in wordlist.
Example:
>>> is_word(wordlist, 'bat') returns
True
>>> is_word(wordlist, 'asdf') returns
False
"""
word = word.lower()
word = word.strip(" !@#$%^&*()-_+={}[]|\:;'<>?,./\"")
return word in wordlist
def random_word(wordlist):
"""
Returns a random word.
wordlist: list of words
returns: a word from wordlist at random
"""
return random.choice(wordlist)
def random_string(wordlist, n):
"""
Returns a string containing n random words from wordlist
wordlist: list of words
returns: a string of random words separated by spaces.
"""
return " ".join([random_word(wordlist) for _ in range(n)])
def random_scrambled(wordlist, n):
"""
Generates a test string by generating an n-word random string
and encrypting it with a sequence of random shifts.
wordlist: list of words
n: number of random words to generate and scamble
returns: a scrambled string of n random words
NOTE:
This function will ONLY work once you have completed your
implementation of apply_shifts!
"""
s = random_string(wordlist, n) + " "
shifts = [(i, random.randint(0, 26)) for i in range(len(s)) if s[i-1] == ' ']
return apply_shifts(s, shifts)[:-1]
def get_fable_string():
"""
Returns a fable in encrypted text.
"""
f = open("fable.txt", "r")
fable = str(f.read())
f.close()
return fable
# (end of helper code)
# -----------------------------------
#
# Problem 1: Encryption
#
def build_coder(shift):
"""
Returns a dict that can apply a Caesar cipher to a letter.
The cipher is defined by the shift value. Ignores non-letter characters
like punctuation and numbers.
shift: -27 < int < 27
returns: dict
Example:
>>> build_coder(3)
{' ': 'c', 'A': 'D', 'C': 'F', 'B': 'E', 'E': 'H', 'D': 'G', 'G': 'J',
'F': 'I', 'I': 'L', 'H': 'K', 'K': 'N', 'J': 'M', 'M': 'P', 'L': 'O',
'O': 'R', 'N': 'Q', 'Q': 'T', 'P': 'S', 'S': 'V', 'R': 'U', 'U': 'X',
'T': 'W', 'W': 'Z', 'V': 'Y', 'Y': 'A', 'X': ' ', 'Z': 'B', 'a': 'd',
'c': 'f', 'b': 'e', 'e': 'h', 'd': 'g', 'g': 'j', 'f': 'i', 'i': 'l',
'h': 'k', 'k': 'n', 'j': 'm', 'm': 'p', 'l': 'o', 'o': 'r', 'n': 'q',
'q': 't', 'p': 's', 's': 'v', 'r': 'u', 'u': 'x', 't': 'w', 'w': 'z',
'v': 'y', 'y': 'a', 'x': ' ', 'z': 'b'}
(The order of the key-value pairs may be different.)
"""
### TODO.
assert shift >= 0 and shift < 27, 'shift %s is not between 0 and 27' % shift
# #numbers.Integral used in case of long integers
assert isinstance(shift, int), 'shift is not an integer'
coder = {}
lowercase_and_space = string.ascii_lowercase + ' '
uppercase_and_space = string.ascii_uppercase + ' '
# Shift letters over shift places
shifted_lowercase_and_space = lowercase_and_space[shift:] + lowercase_and_space[:shift]
shifted_uppercase_and_space = uppercase_and_space[shift:] + uppercase_and_space[:shift]
# Construct Caesar cipher dictionary
# Add uppercase letters first so ' ' will be overwritten to point to lowercase letter
for i in range(len(uppercase_and_space)):
coder[uppercase_and_space[i]] = shifted_uppercase_and_space[i]
for i in range(len(lowercase_and_space)):
coder[lowercase_and_space[i]] = shifted_lowercase_and_space[i]
return coder
print build_coder(3)
def build_encoder(shift):
"""
Returns a dict that can be used to encode a plain text. For example, you
could encrypt the plain text by calling the following commands
>>>encoder = build_encoder(shift)
>>>encrypted_text = apply_coder(plain_text, encoder)
The cipher is defined by the shift value. Ignores non-letter characters
like punctuation and numbers.
shift: 0 <= int < 27
returns: dict
Example:
>>> build_encoder(3)
{' ': 'c', 'A': 'D', 'C': 'F', 'B': 'E', 'E': 'H', 'D': 'G', 'G': 'J',
'F': 'I', 'I': 'L', 'H': 'K', 'K': 'N', 'J': 'M', 'M': 'P', 'L': 'O',
'O': 'R', 'N': 'Q', 'Q': 'T', 'P': 'S', 'S': 'V', 'R': 'U', 'U': 'X',
'T': 'W', 'W': 'Z', 'V': 'Y', 'Y': 'A', 'X': ' ', 'Z': 'B', 'a': 'd',
'c': 'f', 'b': 'e', 'e': 'h', 'd': 'g', 'g': 'j', 'f': 'i', 'i': 'l',
'h': 'k', 'k': 'n', 'j': 'm', 'm': 'p', 'l': 'o', 'o': 'r', 'n': 'q',
'q': 't', 'p': 's', 's': 'v', 'r': 'u', 'u': 'x', 't': 'w', 'w': 'z',
'v': 'y', 'y': 'a', 'x': ' ', 'z': 'b'}
(The order of the key-value pairs may be different.)
HINT : Use build_coder.
"""
### TODO.
def build_decoder(shift):
"""
Returns a dict that can be used to decode an encrypted text. For example, you
could decrypt an encrypted text by calling the following commands
>>>encoder = build_encoder(shift)
>>>encrypted_text = apply_coder(plain_text, encoder)
>>>decrypted_text = apply_coder(plain_text, decoder)
The cipher is defined by the shift value. Ignores non-letter characters
like punctuation and numbers.
shift: 0 <= int < 27
returns: dict
Example:
>>> build_decoder(3)
{' ': 'x', 'A': 'Y', 'C': ' ', 'B': 'Z', 'E': 'B', 'D': 'A', 'G': 'D',
'F': 'C', 'I': 'F', 'H': 'E', 'K': 'H', 'J': 'G', 'M': 'J', 'L': 'I',
'O': 'L', 'N': 'K', 'Q': 'N', 'P': 'M', 'S': 'P', 'R': 'O', 'U': 'R',
'T': 'Q', 'W': 'T', 'V': 'S', 'Y': 'V', 'X': 'U', 'Z': 'W', 'a': 'y',
'c': ' ', 'b': 'z', 'e': 'b', 'd': 'a', 'g': 'd', 'f': 'c', 'i': 'f',
'h': 'e', 'k': 'h', 'j': 'g', 'm': 'j', 'l': 'i', 'o': 'l', 'n': 'k',
'q': 'n', 'p': 'm', 's': 'p', 'r': 'o', 'u': 'r', 't': 'q', 'w': 't',
'v': 's', 'y': 'v', 'x': 'u', 'z': 'w'}
(The order of the key-value pairs may be different.)
HINT : Use build_coder.
"""
### TODO.
def apply_coder(text, coder):
"""
Applies the coder to the text. Returns the encoded text.
text: string
coder: dict with mappings of characters to shifted characters
returns: text after mapping coder chars to original text
Example:
>>> apply_coder("Hello, world!", build_encoder(3))
'Khoor,czruog!'
>>> apply_coder("Khoor,czruog!", build_decoder(3))
'Hello, world!'
"""
encoded_word = ''
for i in range (0, len(text)):
if text[i] not in coder:
encoded_word = encoded_word + text[i]
else:
encoded_word = encoded_word + coder[text[i]]
return encoded_word
print apply_coder('Hello, world!', build_coder(3))
### TODO.
def apply_shift(text, shift):
"""
Given a text, returns a new text Caesar shifted by the given shift
offset. The empty space counts as the 27th letter of the alphabet,
so spaces should be replaced by a lowercase letter as appropriate.
Otherwise, lower case letters should remain lower case, upper case
letters should remain upper case, and all other punctuation should
stay as it is.
text: string to apply the shift to
shift: amount to shift the text
returns: text after being shifted by specified amount.
Example:
>>> apply_shift('This is a test.', 8)
'Apq hq hiham a.'
"""
### TODO.
return apply_coder(text, build_coder(shift))
print apply_shift('This is a test.', 8)
#
# Problem 2: Codebreaking.
#
def find_best_shift(wordlist, text):
"""
Decrypts the encoded text and returns the plaintext.
text: string
returns: 0 <= int 27
Example:
>>> s = apply_coder('Hello, world!', build_encoder(8))
>>> s
'Pmttw,hdwztl!'
>>> find_best_shift(wordlist, s) returns
8
>>> apply_coder(s, build_decoder(8)) returns
'Hello, world!'
"""
### TODO
max_num_valid_words = 0
best_shift = 0
for shift in range(27):
shifted_words = apply_shift(text, shift)
potential_words = shifted_words.split()
num_valid_words = 0
for word in potential_words:
if is_word(wordlist, word):
num_valid_words += 1
if num_valid_words > max_num_valid_words:
max_num_valid_words = num_valid_words
best_shift = shift
return best_shift
print find_best_shift(wordlist, "Pmttw,hdwztl!")
#
# Problem 3: Multi-level encryption.
#
def apply_shifts(text, shifts):
"""
Applies a sequence of shifts to an input text.
text: A string to apply the Ceasar shifts to
shifts: A list of tuples containing the location each shift should
begin and the shift offset. Each tuple is of the form (location,
shift) The shifts are layered: each one is applied from its
starting position all the way through the end of the string.
returns: text after applying the shifts to the appropriate
positions
Example:
>>> apply_shifts("Do Androids Dream of Electric Sheep?", [(0,6), (3, 18), (12, 16)])
'JufYkaolfapxQdrnzmasmRyrpfdvpmEurrb?'
"""
### TODO.
encoded_word = ""
a = dict(shifts)
for key in a:
if key == 0:
initial_shift = apply_shift(text, a[key])
encoded_word = encoded_word + initial_shift
print encoded_word
else:
new_shift = apply_shift(encoded_word[key:], a[key])
print new_shift
encoded_word = encoded_word[0:key] + new_shift
print encoded_word
return encoded_word
# print apply_shifts("JufYkaolfapxQdrnzmasmRyrpfdvpmEurrb?", [(0, 21), (3, 9), (12, 11), (18, 0), (21, 0), (30, 0)])
#
# Problem 4: Multi-level decryption.
#
def find_best_shifts_rec(wordlist, text, start):
"""
Given a scrambled string and a starting position from which
to decode, returns a shift key that will decode the text to
words in wordlist, or None if there is no such key.
Hint: You will find this function much easier to implement
if you use recursion.
wordlist: list of words
text: scambled text to try to find the words for
start: where to start looking at shifts
returns: list of tuples. each tuple is (position in text, amount of shift)
"""
### TODO.
new_string = text
for shift in xrange(0, 27):
new_string = text[0:start] + apply_shift(text[start:len(text)], shift)
for i in xrange(start, len(new_string)+1):
if is_word(wordlist, new_string[start:i]):
if i == len(new_string):
return [(start, shift)]
elif new_string[i] == ' ':
recShifts = find_best_shifts_rec(wordlist, new_string, i+1)
if recShifts != None:
return [(start, shift)] + recShifts
print find_best_shifts_rec(wordlist, "JufYkaolfapxQdrnzmasmRyrpfdvpmEurrb?", 0)
def decrypt_fable():
"""
Using the methods you created in this problem set,
decrypt the fable given by the function get_fable_string().
Once you decrypt the message, be sure to include as a comment
at the end of this problem set how the fable relates to your
education at MIT.
returns: string - fable in plain text
"""
fable = get_fable_string()
shifts = find_best_shifts_rec(wordlist, fable, 0)
decoded_fable = apply_shifts(fable, shifts)
return decoded_fable
print decrypt_fable()