Homework 2
Objectives
- Loop over the characters of a string with
for - Build a new string piece by piece with the accumulator pattern
- Use
range(start, stop, step)to visit every few positions of a string - Use a conditional inside a loop to decide what to do with each character
- Use a named constant instead of a “magic number”
Background
Cryptography studies ways of communicating securely. The two main operations are encryption, which turns a readable plaintext message into an unreadable ciphertext, and decryption, which turns the ciphertext back into the original message. In this assignment you’ll write four short functions that encrypt, decrypt, and check messages using two simple (and very insecure!) schemes.
Every function in this assignment builds or examines a string one character at a time. The pattern you’ll use over and over is the accumulator: start with an empty string before the loop, add a piece to it each time through the loop, and return it after the loop.
result = ""
for ch in message:
result = result + ... # the piece for this character
return resultEach function returns its result – don’t add a print statement inside any of them.
Part 0: Setup
Click “Download Starter Code” above to download the Python file hw2.py, then move it to your cs145/homework folder. Open it from Thonny and add your code in the places specified by the comments. Be sure to delete all comments such as “# YOUR CODE HERE” before you turn in your work.
The starter file defines two constants at the top, ALPHABET and REPEAT, and a complete helper function, shift_letter. Don’t change any of these.
Part 1: fools_encrypt
A friend says she has a great idea for encrypting a message: repeat each character 3 times. So
secret message
becomes
ssseeecccrrreeettt mmmeeessssssaaagggeee
Notice that the space is repeated too.
Write fools_encrypt, which takes a message and returns it encrypted with this scheme. Use the constant REPEAT rather than the number 3 – if your friend decides 4 copies is more secure, changing one line at the top of the file should be enough.
def fools_encrypt(message):
"""
Encrypt a message by repeating each character REPEAT times.
Parameters:
message (str): the message to encrypt
Returns:
str: the encrypted message
Example:
>>> fools_encrypt("hi there")
'hhhiii ttthhheeerrreee'
"""Part 2: fools_decrypt
To decrypt, reverse the process: keep the first character of each group of REPEAT, i.e., for REPEAT = 3 the characters at indices 0, 3, 6, 9, …
Write fools_decrypt, which takes a message encrypted by fools_encrypt and returns the original message. Hint: range can take a third argument, the step: range(0, 10, 3) produces 0, 3, 6, 9. Again, use the named constant REPEAT rather than 3.
def fools_decrypt(secret):
"""
Decrypt a message encrypted with fools_encrypt by keeping every
REPEAT-th character, starting with the first.
Parameters:
secret (str): a message encrypted with fools_encrypt
Returns:
str: the original message
Example:
>>> fools_decrypt("hhhiii ttthhheeerrreee")
'hi there'
"""You can check both functions together: for any message m, fools_decrypt(fools_encrypt(m)) should give back m.
Part 3: caesar_encrypt
Julius Caesar is said to have encrypted his messages by shifting each letter a fixed number of places in the alphabet. With a shift of 3, a becomes d, b becomes e, and so on. Letters near the end wrap around to the beginning.
ALPHABET = "abcdefghijklmnopqrstuvwxyz "Our version uses the 27 characters in ALPHABET: the lowercase letters followed by a space. So with a shift of 3, y becomes a and the space becomes c. The starter file gives you a helper function, shift_letter, that shifts a single character. Try it in the Thonny shell before you go on:
>>> shift_letter("a", 2)
'c'
>>> shift_letter("z", 2)
'a'
>>> shift_letter("c", -2)
'a'shift_letter only works on characters in ALPHABET. Your caesar_encrypt should shift each character that is in ALPHABET and leave every other character (capital letters, digits, punctuation) unchanged. To check whether a character is in ALPHABET, use the in operator, which works on strings:
>>> "q" in ALPHABET
True
>>> "Q" in ALPHABET
FalseWrite caesar_encrypt, which takes a message and a shift and returns the encrypted message.
def caesar_encrypt(message, shift):
"""
Encrypt a message with a Caesar cipher. If a character is in
ALPHABET, it is shifted by shift places. Otherwise, it is left
unchanged.
Parameters:
message (str): the message to encrypt
shift (int): number of places to shift each character
Returns:
str: the encrypted message
Example:
>>> caesar_encrypt("hello world", 3)
'khoorczruog'
>>> caesar_encrypt("Hi, there!", 1)
'Hj,auifsf!'
"""One nice thing about this scheme: the same function decrypts. To undo a shift of 3, shift by -3:
>>> caesar_encrypt("khoorczruog", -3)
'hello world'As a check, decrypt this message, which was encrypted with a shift of 5. It’s a quote from a famous computer scientist (Edsger Dijkstra):
htruzyjwexhnjshjenxestertwjefgtzyehtruzyjwxeymfsefxywtstrcenxefgtzyeyjqjxhtujx
Part 4: is_secret_safe
Since caesar_encrypt leaves capitals and punctuation unchanged, a message like "Meet me at Noon!" isn’t fully hidden: the M, the N, and the ! give some of it away. Write is_secret_safe, which takes a message and returns True if every character is in ALPHABET. Otherwise, it returns False.
def is_secret_safe(message):
"""
Check whether caesar_encrypt will hide every character of a message.
If every character is in ALPHABET (a lowercase letter or a space),
returns True. Otherwise, returns False.
Parameters:
message (str): the message to check
Returns:
bool: True if every character of message is in ALPHABET,
False otherwise
Example:
>>> is_secret_safe("meet me at noon")
True
>>> is_secret_safe("Meet me at noon!")
False
"""Be careful about when you can decide the answer. As soon as you find one character that isn’t in ALPHABET, you know the answer is False. But you can only decide True after you’ve checked every character. Test with a message whose only bad character is the first, one where it’s in the middle, and one where it’s the last. An empty message has no bad characters, so is_secret_safe("") returns True.
Specification
At a minimum your submission should have:
- A function named
fools_encryptwith one parameter, a message, that returns the message with each character repeatedREPEATtimes. - A function named
fools_decryptwith one parameter, a message encrypted byfools_encrypt, that returns the original message. - A function named
caesar_encryptwith two parameters, a message and a shift, that returns the message with each character inALPHABETshifted by that many places and every other character unchanged. - A function named
is_secret_safewith one parameter, a message, that returnsTrueif every character of the message is inALPHABET. Otherwise, it returnsFalse.
Style and Design
- You must add your code to the provided starter file.
- At the top of your file, include a comment with your name (
Author:) and a note on what resources you used (included in starter file). - Docstrings are already provided for every function in the starter file – read each one carefully, since it describes exactly what your function should do.
- Use the constants
REPEATandALPHABETinstead of writing their values directly in your functions. - Follow the Guidelines for Code Quality.
Submitting your work
When you are done, submit hw2.py to Gradescope.
Ensure that the upload succeeds and your submission passes all visible tests. Passing all of the visible tests does not guarantee that your submission correctly satisfies all of the requirements of the assignment. Some tests are hidden until after the grace period; they check the cases this writeup asks you to test yourself, such as wraparound in caesar_encrypt and a bad character at the start, middle, or end of a message for is_secret_safe. We encourage you to submit early and often so you have time to resolve any issues.