Sending Secret Messages with Python

This notebook will teach you how to send secret messages to your friends using a computer language called "Python." Python is used by thousands of programmers around the world to create websites and video games, to do science and math, even this web page is written using Python!

Before we learn any Python, we need to learn just a bit about secret codes. One way to make your message secret is using a "substitution cipher." This means that we replace every letter in our message with a different letter. For example, all of the c's in our message might become j's. The message will look like garbled nonsense to anyone who doesn't know which letter has been substituted. By the time you're done with this page, you'll be able to make this message

this is a super secret message

Look like this

pkbm bm t mexuu mueuup nummtcu

Only someone who knows the password will be able to turn it back into the original message. (By the way, did you notice, above that everywhere there's an 's' in the original message, the coded message has an 'm'?)

To use a substitution cipher, the first thing to do is to decide which letter will replace which. We'll create a table like this one:

Old letter
New letter
AN
BF
CR
DL
EA
FT
GZ
HB
IQ
......

I've only shown part of the table. We'll see the rest later.

The values in your table are not important except for a few rules:

  1. Every letter in the alphabet must be in the left column.
  2. Every letter must be in the right column.
  3. Both you and the person you're sending to must know this table.

To encode using your table is easy. For each letter in your message, instead of writing that letter, find it in the left column of your table, then write down the letter in the same row, but in the right column. For example, if your message is ABBA HIGH DEF, using the table above, you would encode it as NFFN BQZB LAT. Take a minute to make sure I got it right.

How to use this notebook

This lesson is written as a Jupyter notebook. Notebooks are a convenient way to add Python to a website. Wherever you see a box with "In [ ]:" next to it, you can click inside the box, then hold down the Ctrl button and press the Enter button at the same time to make Python run the code inside. Try it with the box below.


In [ ]:
print ("Hello my name is Levi.")

You can also change the code in the boxes before you run them. Try replacing "Levi" with your name in the box above. Now, while the cursor is still in the box, push Ctrl+Enter to see Python echo it back. From here on out, make a habit of executing each box in this notebook with Ctrl-Enter.

Now that you know a little bit about Jupyter notebooks, we're ready to learn some Python!

Python Dictionaries

To get Python to do this work for us, the first thing to do is to decide on a table and create a 'dictionary' in Python that keeps track of which letters get replaced by which new letters. A Python dictionary is a lot like the two-column table we've been talking about. We create an empty table like this:


In [ ]:
table={}
print (table)

Don't go on until you click into the box above and push Ctrl+Enter. You should see {} appear below the box. One more thing about Jupyter notebooks. Each time you execute a box, Python remembers what you've done. Here, we created a dictionary called "table." When we refer to "table" in the next box, Python will remember that "table" is an empty dictionary. The code in the box below adds one entry to your dictionary with the letter 'a' in the left column and 'n' in the right column. Try it out by pushing Ctrl-Enter in the box below.


In [ ]:
table={}
table['a'] = 'n'
print (table)

Don't be afraid to change the code in the boxes. You'll learn a lot by playing around, so don't be shy. Make some changes to the code in the boxes, then push Ctrl-Enter to see what you changed. You might get an error from Python. That's OK. See if you can figure out how to fix it. As you play around, remember that the notebook is remembering your changes. If you want to clear the notebook's memory and start again, look for the menu option above called "Kernel." Select "Restart" from that sub menu.

Quick tip: In the box above, make sure to put the quotation marks around your letters.

Now that our table has some values in it, we can read from it like this:


In [ ]:
newletter = table['a']
print(newletter)

The first line, newletter = table['a'], tells Python to read the entry we gave it for 'a' and call it by a new name, newletter. In this case, table['a'] is 'n'. After that, everytime we refer to newletter Python will remember that we told it newletter is 'n'. So, when we ask Python to print newletter, it remembers and prints 'n'. newletter is what programmers call a 'variable'--a name that refers, in your program, to some other value.

A few more things about our Python tables (also called 'dictionaries'). The values on the left hand side of the table are called the "keys." The right hand are called the "values." Let's add a few rows to our table, then ask Python some questions. Push Ctrl-Enter in the boxes below and see what Python answers.


In [ ]:
table['b'] = 'f'
table['c'] = 'r'
print(table)

In [ ]:
table.keys()

In [ ]:
table.values()

In [ ]:
print(table['c'])
print(table)

The colon (:) separates a "key" from its "value." 'a':'n' means that all the a's become n's during encoding.

What if we ask for a table entry that hasn't been set yet?


In [ ]:
print(table['d'])

Python didn't like that and sent us back a KeyError. To avoid these errors, we can first check to see whether that table entry exists.


In [ ]:
'd' in table.keys( )

We just asked Python, "Is d one of the keys in our table?" Python said, "No." Now, let's create a table entry for 'd' and ask again.


In [ ]:
table['d'] = 'n'
'd' in table.keys()

A key can only appear in the left column one time. If I try to assign one letter on the left column to two different letters on the right, the dictionary will store the last value.


In [ ]:
table['d'] = 'l'
print(table['d'])

The old table entry for 'd' is lost and replaced by the new value. Note that more than one key can correspond to a single value. When we assigned 'd' to be changed to 'n', both 'a' and 'd' were being translated to 'n'. That's perfectly OK.

Python Lists

Way to go! Hopefully, you're feeling comfortable with dictionaries. Make sure to execute the code in the boxes as you go by clicking into the box and pushing Ctrl-Enter.

Next, we'll take a look at lists. A python list is like a dictionary where the keys are numbers, specifically integers (i.e. 0,1,2,3,4,5...). We can create a list like this:


In [ ]:
myList=['t','h','e','q']
print(myList)

The list works a lot like a dictionary with numbers in the left column. We can ask for specific letters in the list by referring to their position in the list. Try this:


In [ ]:
myList[2]

Wait, but isn't 'e' the third letter in the list? It's important to remember that computers start counting from zero.


In [ ]:
myList[0]

In a Python list, position 0 is the very start of the list. Now, there are 4 letters in the list. What do you think will happen if we ask for the letter in position 4?


In [ ]:
myList[4]

What do you think this error means? (Hint: "index" is the number inside the square brackets.)

The first element in our list is number 0. The last is number 3.

A quick note: if Python starts responding to you with errors like 'myList' is not defined it usually means you missed executing one of the code boxes. Make sure you push Ctrl-Enter in each box with a In [ ] next to it.

Python strings are lists

A bunch of text all together in Python is called a "string." Here's a string


In [ ]:
myString = "the quick brown fox jumped over the lazy dogs"
myString

Our string acts just like a list


In [ ]:
myString[2]

The third letter in our string is 'e'. We ask Python for letter number 2 since Python starts counting from zero. Even the spaces in our string are considered letters.


In [ ]:
myString[3]

The output above just looks like two quote marks, but there's actually a space between them. The 4th "letter" in our string is actually a space.

Take a minute to write some code yourself involving strings. You can edit the code in the box above and push Ctrl-Enter to execute it. Can you figure out which letter is 'q'? Or 'b'? What if you pick a number that's too large, like 100?

Creating a cipher table from a sentence

Let's create a table we can use to encode and decode our messages. I want to change all of my a's into t's. How can we create a Python dictionary to do that?


In [ ]:
table={}
table['a']='t'
print(table)

Next, I'll add a row for changing all of the b's.


In [ ]:
table['b']='h'
print(table)

In fact, what I'd like to do is to take an easy to remember phrase and use it to determine the right hand side of my table. I'd like to put 'abcdefghijklmnopqrstuvwxyz' down the left hand column and 'the quick brown fox jumped over the lazy dogs' down the right hand side. So 'a' changes to 't'. 'b' changes to 'h'. 'c' changes to 'e' and so on.

Old letter
New letter
at
bh
ce
dq
eu
fi
gc
hk
ib
......

How can I do this in Python? We can write a loop to create a dictionary from two strings. Let's build the same string as before.


In [ ]:
sentence = "the quick brown fox jumped over the lazy dogs"

Just like before, we can treat it like a list of letters.


In [ ]:
sentence[10]

Now, let's create another string like this.


In [ ]:
everyletter = "abcdefghijklmnopqrstuvwxyz"

We're going to use these two strings to create a table like this

Old letter
New letter
at
bh
ce
dq
eu
fi
gc
hk
ib
jr
ko
lw
mn
nf
oo
px
qj
ru
sm
tp
ue
vd
wo
xv
ye
zr

To create this list in Python, we could do this:


In [ ]:
table={}
table['a']='t'
table['b']='h'
table['c']='e'
table['d']='q'
table['e']='u'
table['f']='i'
table['g']='c'
#And on and on...
table

But what if someone discovered our passphrase ("the quick brown fox...")? We'd have to change each of the individual letters. Instead, we can tell Python to read the letters from the strings, everyletter and sentence, that we created above. Take a look at this. We're using some fancier Python tricks that I'll explain in a minute.


In [ ]:
table={}
table[ everyletter[0]] = sentence[0]
table[ everyletter[1]] = sentence[1]
table[ everyletter[2]] = sentence[2]
table[ everyletter[3]] = sentence[3]
table[ everyletter[4]] = sentence[4]
#And on and on...
table

This requires some explanation. The first line, I think you'll remember, creates an empty table or dictionary. Then, when we write

table[ everyletter[0]] = sentence[0]

Python will look inside the square brackets, [], following "table[" to see which letter to put in the left hand column. Instead of a letter, Python sees everyletter[0]. To Python, this means the first letter in the string called "everyletter"--a. So, Python puts 'a' in the left hand column.

Next, it looks for what to put in the right column after the equal sign. There, Python sees sentence[0] which it knows means the first letter in the string "sentence" or 't'. So, Python creates a row in the table with 'a' in the left column and 't' in the right column. When we started encoding message, we'll turn all the a's into t's.

So, Python will act as if we wrote

 table['a'] = 't'

adding one entry to our translation table.

The line

table[ everyletter[1]] = sentence[1]

creates another new row with the second letter in "everyletter" and the second letter in "sentence." Can you figure out what will be in the left and right columns of this new row?

This is tricky. When learning new programming ideas, it's important to play around a little. Play around with adding rows to your table. What would happen if I wrote


In [ ]:
table[ everyletter[1]] = sentence[0]
print(table)

What changed? Why? This is another good chance to play around by writing your own code.

Loops in Python

Instead of writing 26 lines like table[ everyletter[14]] = sentence[14], I'd like to write it just once, then have the computer repeat it 26 times increasing the number each time. For this, we use a "for loop." Try this


In [ ]:
table={}
for num in range(26):
    table[ everyletter[num]] = sentence[num]
table

Don't worry if this isn't clear yet. We'll take a good look at this. The first line creates an empty dictionary. The next line is the loop

for num in range(26):

In ordinary English, it tells Python to create a list of 26 numbers starting from zero. Then Python executes the next command (table[ everyletter[num]] = sentence[num]) one time for each number in the list. Each time it replaces 'num' with one of the numbers.

Of course, you don't have to use 'num.' You can use anything you want. You could use your name

for maria in range(26):

With this new version, it will replace 'maria' with each of the numbers.

As a side note, you can use any name you want, but in writing computer programs, it's best to be as clear as you can about what all the parts are doing. You never know when you'll have to read your own program to fix problems or add something new. Also, when you write larger programs, you're usually working with a team of programmers. It might be someone else reading and fixing your code. Make sure your program is clear enough for someone else to read and understand.

Let's do a simpler loop first


In [ ]:
for name in ["Bart", "Lisa", "Maggie"]:
    print ("Hi, " + name)

Here, we gave Python a list of three names. For each name on the list, Python executes the commands that follow, replacing "name" with one of the names in the list. This is the first time we've seen + in Python, but it's an easy one. "Hi, " + name, just means that we add each name in the list to the end of "Hi, " and send that to print.

Try editing the loop above. Add your name (maybe it's Homer?). Make a list of your favorite sports teams and make Python cheer for them.

One tricky rule in Python involves indenting. It's possible to put lots of commands inside one for loop, but Python needs to be able to know which commands are in the loop and which are not. Each line that we want to be part of the loop, we indent, but we don't indent the for the loop statement itself. When Python sees a line that's not indented, it knows you've finished writing the loop. Take a look at this. Change the indenting and see what happens. What if you don't indent the "I'll stop you"? What if you do indent "You'll never get away with this"? Try it!


In [ ]:
for villain in ["Kylo Ren", "The Joker", "Magneto", "Megamind"]:
    print ("Oh no! It's " + villain)
    print ("I'll stop you!")
print ("You'll never get away with this.")

The spaces before the print statements tell us whether they're part of the loop to be executed once for each villain, or just a lone command to be executed only once.

What about this?


In [ ]:
for villain in ["Kylo Ren", "The Joker", "Magneto", "Megamind"]:
    print ("Oh no! It's " + villain)
print ("I'll stop you!")
    print ("You'll never get away with this.")

Python didn't like that. The line that says print("I'll stop you!") was not indented. So, Python thought you were done with that loop. When it saw that print("You'll never get away with this.") was indented, it didn't know what loop it was part of. So, it sent an error message. Errors are part of programming. They can be intimidating, but it can help you to sort out the problem if you read the error message. Here, Python told us "unexpected indent" and referred to print("You'll never get away with this."). It clearly didn't expect that line to be indented.

Using Loops to make a table

Now let's use our loops to create a code table


In [ ]:
for num in range(26):
    table[ everyletter[num]] = sentence[num]
table

The "range" function just generates a list of numbers beginning with zero. The code below creates a list from 0 to 4 (5 total numbers), then prints each one.


In [ ]:
for i in range(5):
    print(i)

So, to make our table, we make a list of numbers from 0 to 25, then for each number, Python repeats the command

table[ everyletter[num] ] = sentence[num]

but it replaces 'num' with each of the numbers in the list. So it's exactly the same as writing

table[ everyletter[0] ] = sentence[0]
table[ everyletter[1] ] = sentence[1]
table[ everyletter[2] ] = sentence[2]
table[ everyletter[3] ] = sentence[3]
table[ everyletter[4] ] = sentence[4]
table[ everyletter[5] ] = sentence[5]
...
(skip a few)
...
table[ everyletter[25] ] = sentence[25] 

except we only had to write those two lines. Much easier! (P.S. Why just 25? What happened to 26?)

We can combine everything we've done so far into one script. Read it through carefully and see if you can describe what each line does.


In [ ]:
table = {}
everyletter = "abcdefghijklmnopqrstuvwxyz"
sentence = "thequickbrownfoxjumpedoverthelazydogs"
for num in range(26):
    table[ everyletter[num]] = sentence[num]
table

BREAK TIME!

Congratulations! You're about halfway to the end. Take a minute to stand up, stretch your legs and savor your new knowledge of Python dictionaries, lists and loops. Then, push Ctrl-Enter in the box below to watch the Minions. When you're done, we'll use the table we just created to encode our message.


In [ ]:
from IPython.display import YouTubeVideo
YouTubeVideo("rl6-zJharrs")

Using our table to encode

Welcome back! We're almost ready to encode our message using our table. Start with this for loop. Before you hit Ctrl-Enter, see if you can predict how Python will respond. Then run it and see if you were right.


In [ ]:
for letter in "this is a super secret message":
    print (letter)

That's not very secret yet. This time, instead of printing our each letter of our message, let's look in the table, swap the letter, then print it out. Do the same as before with this code. Can you predict what Python will do?


In [ ]:
for letter in "this is a super secret message":
    print (table[letter])

Oops! It seemed to be doing fine, but we crashed on the 5th letter. Python called it a "KeyError". What do you think that means?

We'll need to do some "debugging." Try modifying your loop like this. This way, we'll see each letter before and after it's encoded. We won't leave this in out final program, but it will help us to find the problem.


In [ ]:
for letter in "this is a super secret message":
    print ("old: " + letter)
    print ("new: " + table[letter])

It's the space! Python is complaining because we asked for the table entry with a space, ' ', in the left column. But there is no such entry. Remember when we called the left hand column the "keys"? A KeyError means we asked for a key that isn't in the table. It even told us the incorrect key was ' '--the space! There are several ways to solve this. What would you do? Take a minute and try it.

One way to solve this is just to add a dictionary entry for ' '.


In [ ]:
table[' '] = ' '

Now, there is an entry for ' '. It just gets replaced by ' ' again. Now our original program works.


In [ ]:
for letter in "this is a super secret message":
    print (table[letter])
    table[' '] = ' '

We did it! This is just what we'd expect if we encoded this message by hand.

We can do a few things to improve it. First, we can create a new string rather than printing our encoded message with one letter on each line. We'll first create a string that has no letters in it. As we encode each letter, we'll add it to the string. Then we'll print it at the end.


In [ ]:
coded_message = ""
for letter in "this is a super secret message":
    coded_message = coded_message + table[letter]
print (coded_message)

Let's look closer at this one, too. At the beginning, coded_message had no letters at all. But at each step of the loop, instead of printing table[letter] we add it to the end of coded_message by writing

coded_message = coded_message + table[letter]

To Python, the equal sign means making a change. Whatever is to the left of the = is the thing we're replacing. To programmers, this 'thing' is called a 'variable'. A variable is a name that represents something else (a number, a word, a dictionary) in your program. In this case, Python knows we want to put something new into coded_message. And what is that something? It's the original coded_message plus table[letter].

Let's consider one more thing. The line print (coded_message) is not indented. Why? What would happen if we indented that? Try it!

We can put this all together to have a single program that creates our table and encrypts our message


In [ ]:
table = {}
everyletter = "abcdefghijklmnopqrstuvwxyz"
sentence = "thequickbrownfoxjumpedoverthelazydogs"
for num in range(26):
    table[ everyletter[num]] = sentence[num]
table[' '] = ' '
coded_message = ""
for letter in "this is a super secret message":
    coded_message = coded_message + table[letter]
print (coded_message)

This is pretty slick. All we have to do is replace "this is a super secret message" with our own and Python does the rest of the work. Now, it's time to play around. Change the script above to encrypt a message of your own. One word of warning. We're not ready to use capital letters or punctuation yet. Use just lower case letters.

You can also try replacing "thequickbrownfox.." with your own sentence. What if your sentence is all x's ("xxxxxxxxxxxxx")?

Here are some other things to try:

  • Try encoding "abcdefghijklmnopqrstuvwxyz" as your secret message? Where did the "lazy dogs" go?
  • Encode your message twice to make it harder to crack. You can do this by putting your encoded output in as your secret message, but there's an easier way. What if you replace the line

      coded_message = coded_message + table[letter]

with

     coded_message = coded_message + table[table[letter]]

What do you expect it to do? See if you can predict the result for a short message like your name.

  • Can you decode your message? What happens if your switch the alphabet and the sentence? Why?

Here's another copy of the same code so you'll still have a clean copy to refer to as you play around.


In [ ]:
table = {}
everyletter = "abcdefghijklmnopqrstuvwxyz"
sentence = "thequickbrownfoxjumpedoverthelazydogs"
for num in range(26):
    table[ everyletter[num]] = sentence[num]
print (table)
table[' '] = ' '
coded_message = ""
for letter in "my teacher is a handsome genius":
    coded_message = coded_message + table[letter]
print (coded_message)

Getting Input From the User

It's a lot of work to modify your program for every new message you want to encode. Let's give the message another name like "uncoded_message"


In [ ]:
coded_message = ""
for letter in uncoded_message:
    coded_message = coded_message + table[letter]
print (coded_message)

Uh-oh! Can you find and fix the problem above?

We need to assign uncoded_messageto be something. We can add a line like this:

uncoded_message = "this is a super secret message"

Add it to the program above and see how it works. Make sure to add it before the loop. Python runs your commands starting from the top. Before it can use the variable uncoded_message it needs you to tell it what uncoded_message is.

Instead of having the secret message in the program, there is a way to ask the user to provide the message. The command for this is "input." Try this.


In [ ]:
name = input("What is your name? ")
print ("Well, hello, " + name)

Important note for instructors: If students are getting an error above, you may be using Python version 2.7. If you can switch to Python 3, it will work better. Otherwise, you can get around these errors by putting your answers to "input" in quotation marks. For example, when it asks, What is your name? type "Marcus" with the quotation marks.

Let use the same thing for getting a secret message. Modify the code below to use input to create uncoded_message.


In [ ]:
uncoded_message = input("What message should I encode?")
coded_message = ""
for letter in uncoded_message:
    coded_message = coded_message + table[letter]
print (coded_message)

Now, let's add this to our main program. One quick tip. For now, when our program asks for input, you might want to use all lower case letters--no capitals and no punctuation. You're welcome to try anything you want, but don't be frustrated by the error messages. We'll discuss the reason for those a little later.


In [ ]:
#First, create our substitution table
table = {}
everyletter = "abcdefghijklmnopqrstuvwxyz"
sentence = "thequickbrownfoxjumpedoverthelazydogs"
for num in range(26):
    table[ everyletter[num]] = sentence[num]
table[' '] = ' '

#Get a message from the user
uncoded_message = input("Type your message here, then press enter: ")

#Encode and print the message
coded_message = ""
for letter in uncoded_message:
    coded_message = coded_message + table[letter]
print (coded_message)

In the above code, we made a few other changes. Can you see them? We added some lines beginning with #. When we begin a line with #, we're telling Python that the line is just for other humans to read and Python ignores it. So, we can put whatever we want there and it won't affect our program. It's a good idea to put comments in your program so someone else knows what you're doing.

Decoding your message

Our encoded messages aren't much good if we can't decode them! To decode a message encoded with a substitution cipher, we switch the columns of the table. If during encoding, we replaced all the a's with t's, then to decode we should change the t's back into a's. But our table generated with the "quick brown fox" sentence creates a problem. Here it is below.

Old letter
New letter

Old letter
New letter
ATNF
BHOO
CEPX
DQQJ
EURU
FISM
GCTP
HKUE
IBVD
JRWO
KOXV
LWYE
MNZR

In this table, 'c', 'u' and 'y' are all being changed into 'e'. So, when my encoded message contains an 'e', how will I know whether if began as a c, u or y?

We can solve this by making sure each letter appears only once in 'sentence.' We could, for example, assign sentence to be 'thequickbrownfxjmpdvlazygs'. This is easy to do and gives a well-behaved table, but with just a little thought, we can get Python to remove the duplicate letters for us.

'if' statements

An 'if' statement will do the trick here. 'If' statements allow us to provide two sets of instructions and letting Python decide which set to run based on a test that we determine. In our program, we're going to tell Python to consider each letter of our sentence individually. If the letter is not already part of our table, then use it. If not, ignore it. But let's start with an easier one first. Run the box below with Ctrl-Enter.


In [ ]:
name = "Luke"
if name is "Luke":
    print ("May the force be with you, " + name)

That looks easy enough.


In [ ]:
name = "Anakin"
if name is "Luke":
    print ("May the force be with you, " + name)

Aha. No message that time. Notice that the indenting rules are the same for "if" statements as for "for" statements. As long as you indent, Python will treat each new statement as part of the "if" clause and only execute it if the "if" condition is true. What if I'm not Luke?

Next, let's let the user decide her own name.


In [ ]:
name = input("What is your name? ")
if name == "Luke":
    print ("May the force be with you, " + name)

You should get a message only if you tell Python your name is Luke. There's one new idea here. I used "==" instead of "is" here. In Python, these two mean almost the same thing. Usually you can get away with using either, but it's a good idea to always use == when comparing two strings. This is particularly true when one string is returned by input.

We could do this test for a list of names. Can you modify the code above to repeat this for a list of names, say

["Leia", "Luke", "Anakin"]  

and only greet Luke? It's a little bit tricky. If Python gives you an error message, be sure to read it. It often gives you a clue about how to fix the problem. If you give up, scroll down to see my answer.


















In [ ]:
for name in ["Luke", "Leia", "Anakin"]:
    if name is "Luke":
        print ("May the force be with you, " + name)

We combined the for loop that we already know with the if statement we're just learning about. We made a list and for every name in the list we first checked if that name is "Luke", then sent a message only to Luke.

The indentation was tricky here. Look at the line that starts with print. We indented that one a long way! That's because we wanted that line to be part of the "if" code. But the "if" statement was already indented because it's in the for loop. So we indented it twice. If we don't indent enough, we'll get an error from Python. Try it. One rule about Python is that after a line that ends with a colon (like if and for statements) we always have to indent one more level.

Poor Leia. It's a shame to leave her out. (Spoiler alert: she's Luke's sister). We can include her in one of two ways. Python understands the word "or", so we can give Python two names.


In [ ]:
for name in ["Luke", "Leia", "Anakin"]:
    if name is "Luke" or name is "Leia":
        print ("May the force be with you, " + name)

The other way is to explicitly leave Anakin out. Python also understands "not", but in a funny way. Watch.


In [ ]:
for name in ["Luke", "Leia", "Anakin"]:
    if not name is "Anakin":
        print ("May the force be with you, " + name)

Python first tests

name is "Anakin"

then the "not" tells Python to reverse that decision and execute the code only if the name is not Anakin.

Removing our duplicates with "if"

Now, let's use an if loop to remove the duplicates from our sentence. It looks a lot like our greeting loop above.


In [ ]:
sentence = "the quick brown fox jumped over the lazy dogs"
passphrase = ""
for letter in sentence:
    if not letter in passphrase:
        passphrase = passphrase + letter
print (passphrase)

That looks almost right. It would be perfect if not for that lousy space. To get rid of that, we can use "and" to test two different things. With "or" we only needed one of the two tests to be true ("either Luke OR Leia") but with "and", both statements must be true. Try this.


In [ ]:
sentence = "the quick brown fox jumped over the lazy dogs"
passphrase = ""
for letter in sentence:
    if letter not in passphrase and letter is not ' ':
        passphrase = passphrase + letter
print (passphrase)

Perfect. Let's incorporate this into our code. In the code below, I've left a tricky "bug". A "bug" is what programmers call a mistake in the code that causes the program not to work right.

Because of our bug, we seem to be getting the very same output. Try using "cuy" as your message. Why are c and y both still being changed to 'e'? To get really confused, try making your message "jdatddr". What's going on there? You might want to add some "print" statements to help you see what's going on.


In [ ]:
#First, create our substitution table
table = {}
everyletter = "abcdefghijklmnopqrstuvwxyz"

sentence = "the quick brown fox jumped over the lazy dogs"
#Remove the duplicate letters 
passphrase=""
for letter in sentence:
    if letter not in passphrase and letter is not ' ':
        passphrase = passphrase + letter
print (passphrase)

#Build the table
for num in range(26):
    table[ everyletter[num]] = sentence[num]
table[' '] = ' '

#Get a message from the user
uncoded_message = input("Type your message here, then press enter: ")

#Encode and print the message
coded_message = ""
for letter in uncoded_message:
    coded_message = coded_message + table[letter]
print (coded_message)

Ouch! Errors again. Unfortunately, lots of computer programming is finding problems ("bugs") in your code. Try to solve the problem above. It might help to add some "print" statements.

If you couldn't find the problem above (which is OK. It's a hard bug to see), I'll tell you that we've gone to the trouble of creating a "passphrase" with no spaces or duplicates, but then we still used "sentence" to create our table. In place of the line

table[ everyletter[num]] = sentence[num]

write

table[ everyletter[num]] = passphrase[num]

See if you get better answers.

Decoding

At last, we're ready to do some decoding. Suppose your friend encoded a message with the program above and sent it to you. The message you got was "nuuv nu bf vku jtpo tv dby vkbpvg". How would you decode it?

Well, if we can build the same table, we can use it in reverse. In our new table m's become n's during encoding. So, during decoding, I change all the n's back to m's. All the e's became u's so I change all u's back to e's. The t's were changed to v's, so I change them back to t's and so on. Already I know that the first two words of the message are "meet me". Here's the full table so you can decode the rest of the message. Once you're done, we'll see how to do get Python to do it.

Old letter
New letter

Old letter
New letter
ATNF
BHOX
CEPJ
DQQM
EURP
FISD
GCTV
HKUL
IBVA
JRWZ
KOXY
LWYG
MNZS

The hardest part of decoding was having to search through the "New letter" column for each letter. Other than that, it was exactly like encoding except that we're finding letters in the "New letter" column, then replacing them with the letter in the "Old letter" column. So, we can use python to decode by just switching the columns. In the language of Python dictionaries, we need to switch the "keys" and "values." Remember that, for a dictionary, the "keys" were always on the left side of the equal sign and in the parentheses. The values were to the right of the equal sign. So, instead of building our table like this

for num in range(26):
    table[ everyletter[num]] = passphrase[num]

we swap "everyletter" and "passphrase"

for num in range(26):
    table[ passphrase[num]] = everyletter[num]

It's just that easy and we've got a decoder. Make the change yourself here and check it with the message above ("nuuv nu bf vku jtpo tv dby vkbpvg"). What's the decoded message?


In [ ]:
#First, create our substitution table
table = {}
everyletter = "abcdefghijklmnopqrstuvwxyz"

sentence = "the quick brown fox jumped over the lazy dogs"
#Remove duplicate letters
passphrase=""
for letter in sentence:
    if letter not in passphrase and letter is not ' ':
        passphrase = passphrase + letter
print (passphrase)

#Build the table
for num in range(26):
    table[ everyletter[num]] = passphrase[num]
table[' '] = ' '

#Get a message from the user
uncoded_message = input("Type your message here, then press enter: ")

#Encode and print the message
coded_message = ""
for letter in uncoded_message:
    coded_message = coded_message + table[letter]
print (coded_message)

(Did you forget to switch "everyletter" and "passphrase"?)

Finishing up

As our final task, we're going to give the user the option to encode or decode with the same script. Let's use "input" to ask the user whether she's encoding or decoding. If she's decoding, let's leave the table just like it is above. If she's encoding, let's rebuild the table like we did originally. Try it yourself on the code below. My answer is beneath that.


In [ ]:
#First, create our substitution table
table = {}
everyletter = "abcdefghijklmnopqrstuvwxyz"

sentence = "the quick brown fox jumped over the lazy dogs"
#Remove duplicate letters
passphrase=""
for letter in sentence:
    if letter not in passphrase and letter is not ' ':
        passphrase = passphrase + letter
print (passphrase)

#Build the table
for num in range(26):
    table[ passphrase[num]] = everyletter[num]
table[' '] = ' '

#Get a message from the user
uncoded_message = input("Type your message here, then press enter: ")

#Encode and print the message
coded_message = ""
for letter in uncoded_message:
    coded_message = coded_message + table[letter]
print (coded_message)

In [ ]:
#First, create our substitution table
table = {}
everyletter = "abcdefghijklmnopqrstuvwxyz"

sentence = "the quick brown fox jumped over the lazy dogs"
#Remove duplicates
passphrase=""
for letter in sentence:
    if letter not in passphrase and letter is not ' ':
        passphrase = passphrase + letter
print (passphrase)

#Build a table for decoding
for num in range(26):
    table[ passphrase[num]] = everyletter[num]
table[' '] = ' '

#****  This is the new part. If we're encoding, we rebuild the table
#         But we switch everyletter and passphrase
task = input("Are you encoding or decoding?")
if task == "encoding":
    #Build a table for encoding instead
    print ("Remaking table for encoding...")
    for num in range(26):
        table[ everyletter[num]] = passphrase[num]
        
print (table)

#Get a message from the user
uncoded_message = input("Type your message here, then press enter: ")

#Encode and print the message
coded_message = ""
for letter in uncoded_message:
    coded_message = coded_message + table[letter]
print (coded_message)

So, we've got a working encoder/decoder. Try decoding the message we had before ("nuuv nu bf vku jtpo tv dby vkbpvg"). You can send secret messages to your friends. Whether or not they know Python, they can use this program to keep your messages secret.

There's still lots left to do. Here are a few tasks for you to try.

  • Can you put some comments in the code so that others better understand what each part is doing?
  • Try putting a capital letter in your message. What happened? Try using google to figure out how to capitalize and uncapitalize your message.
  • You will also run into trouble if your message has punctuation like a period, comma, question mark or apostrophe. Can you solve that problem?
  • What if you replace "thequickbrownfoxjumpedoverthelazydogs" with your own passphrase? You're liable to run into trouble here for a few reasons. I chose that sentence because it contains every letter of the alphabet. Your passphrase is liable to miss a letter or two. What could you do to guarantee your table doesn't miss any letter even if they're missing from your passphrase?
  • Once you can handle different passphrases, why not take your passphrase from the user using input? This keeps others from reading your messages, but you and your friend have to have agreed on a passphrase ahead of time. Even if you haven't, you can use information that only the two of you know. For example, you could send them the encoded message, then tell them to use her dog's name plus your favorite singer as a passphrase.

With those few tools, you can do a lot with Python. But we've only scratched the surface of what Python can do. There are lots of lessons on sites like codecademy.com, code.org and khanacademy.org to teach you all kinds of things. Happy coding!

© Copyright 2016 Levi Barnes www.levibarnes.com