I'm working my way through the finger exercises in John Guttag's book Introduction to Python Programming, third edition. The following finger exercise on page 143 describes encryption/decryption with a book cipher. The first exercise was to implement decoder
and decrypt
in the same style as encryption is working.
The book mentions this bug and gives the task to implement a solution like they hinted at:
The bug:
When a character occurs in
plain text
but not in thebook
, something bad happens. Thecode_keys
dictionary assigns-1
to each such character, anddecode_keys
assigns-1
to the last character in the book, whatever that may be.The solution:
Create a new book by appending something to the original book.
My solution new_book
uses a lambda function to be in line with the rest of the code. But this overturns the principle of encryption, if the plain text is needed during encryption and decryption
# Mapping individual, unique letters of plaintext to index number
gen_code_keys = (lambda book, plain_text:
{c: str(book.find(c)) for c in plain_text})
# Slicing operator at the end to remove the first '*' at the beginning
encoder = (lambda code_keys, plain_text:
"".join(["*" + code_keys[c] for c in plain_text])[1:])
# Encrypt plain_text by giving the encoder a book for indexing
encrypt = (lambda book, plain_text:
encoder(gen_code_keys(book, plain_text), plain_text))
# Decode key generator analogue to gen_code_keys
gen_decode_keys = (lambda book, cipher:
{c: book[int(c)] for c in cipher.split("*")})
# Implement decoder by looping through all numbers in cipher
decoder = (lambda decode_keys, cipher:
"".join((decode_keys[c] for c in cipher.split("*"))))
# Decrypting the cipher analog to encrypt function
decrypt = (lambda book, cipher:
decoder(gen_decode_keys(book, cipher), cipher))
Don_Quixote = "In a village of La Mancha, the name of which I have no " \
"desire to call to mind, there lived not long since one of those " \
"gentlemen that keep a lance in the lance-rack, an old buckler, a lean " \
"hack, and a greyhound for coursing."
# --------------- My Solution --------------- #
# Creating new book with missing letters in book to fix cipher bug
new_book = (lambda book, plain_text:
book + "".join([c for c in plain_text if c not in book]))
# plain text contains the letters: !ABENQjx
plain_text = "No joke, Abraham Boston had six beer. Everyone it Q!"
# Create new book by appending missing letter from plain_text
book = new_book(Don_Quixote, plain_text)
print(plain_text)
cipher = encrypt(book, plain_text)
print(cipher)
print(decrypt(book, cipher))
Ok, so I changed my code so far that I create the updated book with the missing characters appended before encrypting/decrypting. Thus I removed the function call new_book
inside the encrypt
and decrypt
function. With that at least I don't need the plain_text
in my decrypt
function.
Are there further improvements I could make?
N*34*123*j*…
But then: what if you want to encrypt numbers, and your book doesn’t contain all of the digits? \$\endgroup\$