Chapter 6
Secret Codes

Image

Now that you have mastered if statements, there is only one major topic left to learn. And so in this chapter we’re going to start to explore loops.

Lists

Loops are super important. Different types of loops, and how to properly use them, is the focus of the next few chapters.

But, before we look at loops, let’s take a few moments to revisit a special type of variable, the list. We briefly showed you lists already. Back in Chapter 3 you used this code:

choices=["Heads","Tails"]

As we explained then, variables usually store a single value. Lists are a special type of variable that can store lots of values (as well as 0 values).

The code above is from Chapter 3, and it creates a list named choices that has two items in it: the string Heads and the string Tails.

Creating Lists

So, how do you create a list in Python? Well, as you have already seen, you can simply create a variable to create a list. What makes it a list? If the values you store in the variable are enclosed with [ and ] (square brackets), then you’ve created a list.

So this line of code will create an empty list:

animals=[]

animals is a variable that is a list, but it is empty.

This line of code creates a list with five items in it:

animals=["ant","bat","cat","dog","eel"]

List items must all be enclosed within the square brackets, and they must be separated by commas.

Let’s give this a try. Create a file named List1.py and type this code:

# Create a list
animals=[]
# How many items in it?
print(len(animals), "animals in list")

Save and run the code. You’ll see the text 0 animals in list displayed in the Terminal window.

What does the code do? animals=[] creates an empty list. The print() statement displays how many items are in the list. To do this, it uses the len() function. len() returns the length of whatever item is passed to it, like the number of items in a list. So len(animals) returns the number of items in the list animals. Because the list is empty, it returns 0.

Update your code to look like this (you can use your own list of animals, it doesn’t have to be ours):

# Create a list
animals=["ant","bear","cat","dog","elephant"]
# How many items in it?
print(len(animals), "animals in list")

Save and run this updated code. The output will tell you that the list contains five animals.

Accessing List Items

Lists are designed to store, well, lists, like our list of animals. For a list to be useful, you need to be able to access the items stored in it. To do this, you once again use square brackets, specifying the item number you want.

Let’s try that. Create file List2.py. Here’s the code:

# Create a list
animals = ["ant","bat","cat","dog","eel"]
# Display a list item
print(animals[1])

Save and run the code. What animal gets displayed? The position of an item in a list is called an index. In the print() function, we specified the index 1, which is bat. Why? Because, as you know, Python always starts counting at 0, so ant is in position 0, and bat is in position 1.

Image New Term

Index The position of an item (within a list, for example) is its index.

Try updating and running the code with different index values. Then try using an index that is too high (like 5 if you are using the same example). Python won’t like that, it will tell you that list index out of range, which means the index you provided isn’t valid.

Changing List Items

You’ve seen that you can refer to specific items in a list by their index. We used this to get an item from the list, but the same syntax can be used to update an item in the list.

Create a file named List3.py. Here is the code:

# Create a list
animals = ["ant","bat","cat","dog","eel"]
# Display the list
print(animals)
# Update item 2
animals[2] = "cow"
# Display the list
print(animals)

Save and run the code. It will display a list of animals and then display the list again but with cat replaced by cow. This line of code:

animals[2] = "cow"

saves cow into the animals list in the third spot (once again, starting from 0, so that 2 is the third item). It does not add an item but overwrites the existing value. Bye bye, cat; hello, cow!

Adding and Removing Items

All of the lists we’ve used so far were created and initialized with a set of values. But what if you need to add or remove values on-the-fly?

Let’s look at adding an item first. This is the code for List4.py:

# Create a list
animals = ["ant","bat","cat","dog","eel"]
# How big is the list?
print(len(animals), "animals in list")
# Add an item
animals.append("fox")
# Now how big is the list?
print(len(animals), "animals in list")

Save and run the code. It will report five animals in the list and then six animals in the list.

Why? The animals list does indeed start with five animals, and so the len() in the print() statements returns 5.

But then comes this line:

animals.append("fox")

The append() function adds an item to the end of the list. So the next print() statement says there are six animals as that is what len() will return.

How do you remove an item from a list? There are two functions that can do that. If you know the exact index you want to remove, you can use the pop() function, like this:

animals.pop(5)

If you want to remove an item by its value, you can do this:

animals.remove("fox")

Finding Items

What if you want to check if a value is in a list? There are a couple of ways to do this.

If you just wanted to know if the list contains a value and don’t care about exactly where in the list it is, then you can use a simple if statement.

Create the file List5.py with this code:

# Create a list
animals = ["ant","bat","cat","dog","eel","fox"]
# Is "goat" in the list?
if "goat" in animals:
    # Yes it is
    print("Yes, goat is in the list.")
else:
    # No it isn't
    print("Nope, no goat, sorry.")

Save and run the file. The text Nope, no goat, sorry. will be displayed. Add "goat" to the animals list (on line 2) and then run the code again, this time it’ll display Yes, goat is in the list.

You saw lots of if statements in the previous chapters. Here the if uses an in clause, which, exactly as its name suggests, returns True if the value on the left can be found anywhere in the list and False otherwise.

If you want to know exactly where in the list an item is, you can use the index() function. Update the code to look like this (the only change is on line 6, in the first print() statement):

# Create a list
animals = ["ant","bat","cat","dog","eel","fox"]
# Is "goat" in the list?
if "goat" in animals:
    # Yes it is
    print("Yes, goat is item", animals.index("goat"))
else:
    # No it isn't
    print("Nope, no goat, sorry.")

Save and run the code. If there is no goat in animals, it’ll behave exactly as it did before. But if there is a goat in animals, the display will tell you where in the list it is because animals.index("goat") returns the index of goat.

Sorting

Lists don’t have any specific order. They store and display items however they were added.

All of the lists we used in this chapter were alphabetically sorted. They didn’t have to be. We did that because it makes it easier for you to read and write the code.

But what if you actually do want the list in order? Imagine you have code like this:

# Create a list
animals = ["iguana","dog","bat","eel","goat","ant","cat"]

This list is definitely not in alphabetical order. What if it needed to be?

Now, granted, this isn’t a great example because you could have just typed the animals in alphabetical order, right? True. But what if the list was not hardcoded and the user was typing animals, and then you needed to sort the list when they were done?

Image New Term

Hard Coding When values (numbers, text, dates, all sorts of stuff) are typed into the actual code, we say that they are hard coded. And, as a rule, hard coding anything is bad.

Create file List6.py; here’s the code (feel free to add more animals than we’ve done here; the more, the better):

# Create a list
animals = ["iguana","dog","bat","eel","goat","ant","cat"]
# Display the list
print(animals)
# Sort the list
animals.sort()
# Display the list
print(animals)

Save and run the code. You’ll see the complete list of animals displayed twice: first in the order in which they were put into the list and then alphabetically.

The magic in the code is this line:

animals.sort()

sort() is a function that does just that, it sorts the list. By default, it sorts alphabetically, but you can also make it sort in reverse order and more if needed.

Fun stuff, huh? Ok, so what does this all have to do with loops?

Loop-de-Loop

Thus far, you have learned that Python executes code line by line. It starts at the top of a file, ignores comments, and processes each line in order. In Chapter 4 we introduced if statements, which effectively allow lines of code to be included or excluded in the processing.

But what if you want to repeat a block of code over and over? Perhaps you are writing a game, and you need to allow movements over and over until an obstacle is reached. Or maybe your users can take selfies and send messages over and over until a chat session is closed. Or perhaps it’s something as simple as a calculator app that lets users enter numbers over and over until they hit the Calculate button.

All of these examples have one thing in common: They allow the same functionality to be used over and over until the process has completed.

Coding these actions requires the use of loops, and Python supports two types of loops:

  • You can loop through a defined set of options. This might be looping from 1 to 10, or looping through a set of uploaded images, or looping through the lines of a file you are reading. In this type of loop, the number of iterations (that means how many times the loop loops) is finite. You are looping through a set of options and the loop ends once the last option has been reached. We’ll focus on this type of loop in this chapter.

  • You can also loop until a condition changes. For example, allowing a user to move in a game until their character dies, the loop repeats so long as a condition (character is still alive) is True, and ends when the condition is not (bye bye, character). Or allowing a user to take selfies until they hit Send, the loop allows the camera to be used and pictures to be taken so long as the condition (user has not clicked Send yet) is True; as soon as they click Send, the condition becomes False, and the loop ends. In this type of loop, the number of iterations is not known; the loop just keeps going and going and going until the condition changes. We’ll look at this type of loop in the next chapter.

Looping Through Items

Let’s start with the simplest of loops, looping through a list of items:

animals=["ant","bat","cat","dog","eel"]

You know what this is: It’s a list called animals containing five items in it.

Earlier in this chapter, we saw how to access individual list items. But what if you want to loop through the list so as to print each one individually? Tada! Loops to the rescue!

Create a new file named Loop1.py and type the following:

# List of animals
animals=["ant","bat","cat","dog","eel"]

# Loop through the list
for animal in animals:
    # Display one per loop iteration
    print(animal)

Save and run this code. You should see output like this:

Image

You know what the first line of code does, so let’s look at the loop code. This line:

for animal in animals:

tells Python to loop through animals and, in each iteration, to put the next item in a variable named animal. Note that there are now two variables used here: animal and animals. animals is the list we created, but what is animal? We didn’t explicitly create the animal variable; the for loop code did that for us, and the loop changes the value of animal automatically on each iteration. We told the for loop what to name the variable by specifying for animal. (We could have named the variable anything, but animal seemed like a good choice for a variable that holds one animal from a list of animals.)

Image New Term

Iteration Each cycle of a loop is called an iteration. You may also hear coders say the code iterates, which means it loops.

Just like if statements, loops end with a colon (the : character), and then whatever is indented beneath the loop is what gets called once per iteration.

So, in our example, how many times does the print() statements get called? Five—because there are five items in the list animals. Try adding an item or a few, and run the code again. The indented code will always be called once per item in the list.

And just like with if statements, you need to be careful with indentation. Mess it up, and the loop won’t work as intended. For example, if the loop code looked like this:

# Loop through the list
for animal in animals:
    # Display one per loop iteration
    print("Here is the next animal:")
print(animal)

the output would look like this:

Image

Why? The indented print() statement will be called once per iteration, so five times. But the last print() statement is not indented (coders will say that it is outside of the loop), so it isn’t processed until loop processing has completed. Therefore, that last print() gets called only once, and the variable animal then will contain the last value that was put in it (the final item in the list).

Looping Through Numbers

Next let’s look at looping through numbers. This is similar to the list loop we just saw, but instead of looping through a list we specify a set of numbers and loop through them.

Create the file Loop2.py. Here is the code:

# Loop from 1 to 10
for i in range(1, 11):
    # Display i in each loop iteration
    print(i)

Save and run the code, and you’ll see numbers 1 through 10 displayed, one per line, in the Terminal window.

range() specifies the range of numbers, and just like randrange() in Chapter 3, the end number is not included, so range(1, 11) means start at 1 and stop before you reach 11.

for i creates a variable named i, and inside the loop, i contains the next item in the range. On the first iteration i will be 1, then 2, then 3, and so on.

Try changing the range values and then run your code. Try that a few times.

Nested Loops

Now let’s make things more interesting. At the end of Chapter 4 we showed you a nested if statement, which is an if statement inside of another if statement. You can do the same with loops.

Let’s try an example that will bring back fond memories of your earliest years in school. Remember studying your multiplication tables? Fun, right? It might have taken you a while to memorize all the way to 12 x 12, but Python can do that for you in just three lines of code!

Create a file named Loop3.py and type this code:

# Loop from 1 to 12
for i in range(1, 13):
    # Loop from 1 to 12 within each iteration of the outer loop
    for j in range(1, 13):
        # Print both loop values and multiply them
        print(i, "x", j, "=", i*j)

Save and run the code. You’ll see 144 lines of output fly by, starting with 1 x 1 = 1 all the way to 12 x 12 = 144.

Ok, so how does this code work? We have two loops here, so to keep things clear, let’s call them outer loop and inner loop.

The outer loop uses range(1, 13), so everything indented beneath it gets called 12 times, and each time, variable i will contain the current outer loop iteration number (first 1, then 2, and so on).

The inner loop also uses range(1, 13), and so everything indented beneath it also gets called 12 times, and each time, variable j will contain the current inner loop iteration number.

So how many times does that print() statement get called? The outer loop makes the inner loop execute 12 times. And each time the inner loop executes, it executes the print() statement 12 times. So print() gets executed 144 (12 times 12) times in total.

The print() statement itself is pretty simple:

print(i, "x", j, "=", i*j)

The first time this runs, i will be 1, and j will be 1, so this is effectively:

print(1, "x", 1, "=", 1*1)

print() does the math on-the-fly and will display 1 x 1 = 1.

The next time around, i will be 1, and j will be 2, so the output will be 1 x 2 = 2. This will continue until j is 12 and the displayed text is 1 x 12 = 12. Then the inner loop will have finished, and the outer loop will restart the inner loop a second time; this time, i will be 2. So on the next iteration, it will display 2 x 1 = 1 and so on, all the way until i is 12 and j is 12 and the output is 12 x 12 = 144.

And all in just three lines of code. (Yes, we know there are six lines above, but three of them are comments. Python ignores them, so we can, too!)

Cracking the Code

Now that you know how to use loops, let’s create programs to encrypt and decrypt text. Yes, we’ll create two programs. The first will ask the user for some text and will display an encrypted version of that text. The other will ask the user for encrypted text and will decrypt it and display the original text. And so long as the same secret key (more on that in a moment) is used for encryption and decryption, all will work well.

So, if you receive this text:

Fphjsp#jw!hxrm%

you could decrypt it and it’ll say…um, nope, can’t tell you, sorry! You’ll find out soon….

Ok, a warning. The following code may look complicated. But don’t panic: It is using stuff you’ve already learned. Ready?

Encrypting Characters

Replacing characters with their encrypted version requires doing a little math. Math, you say? Letters aren’t numbers, so how can you do math on them?

Well, as it so happens, inside of your computer letters are indeed numbers. Every single letter and character has an internal number. You don’t usually care about these; to us, letter A is just A, b is just b, and 3 is just 3. But inside of your computer, the letter displayed as A is character number 65, b is 98, and the character for number 3 is 51. Every single character has its own number; so a and A have different numbers because they are different characters.

Yep, this sounds odd, but just accept it for now. Every character has a number (called an ASCII Character Code) that can be used to refer to it.

Image Tip

Use Test Files Every coder has dozens of test files (usually named test42.py, or something like that). Sometimes coders have whole test folders (yep, often named test). Test files are great places to tinker and try stuff out.

In Python you can get the ASCII code for any character by using the ord() function. Run this code. (You have test Python files, right? They are perfect for this):

print(ord('A'))

and it will display 65, the ASCII code for A. Change that A to a B, and it’ll display 66. And so on.

As ord() returns a number, you can do math with it. Not that this next example is particularly useful, but this code:

print(ord('A') + 1)

will display 66: ord('A') returns 65, and the program adds 1 and you get 66.

So how do you turn this number back into a character? The opposite of the ord() function is the chr() function. This code:

print(chr(65))

will display the letter A.

So, if you wanted to add 1 to A to get B, you could do this:

print(chr(ord('A')+1))

What does this do? It is easiest to read it from the inside working outward. ord('A') returns 65, as you saw previously. Add 1 and the total is 66. That sum, 66, gets passed to chr(), which returns B.

We can use this technique to encrypt your text. To encrypt the text HELLO, we just need to know how much to add to (or subtract from) the ASCII value of each character. If we add 10, then HELLO becomes encrypted as ROVVY (H becomes R, E becomes O, and so on). Subtract 10 from each letter in ROVVY, and you get the decrypted HELLO.

Modulus Math

In the example we just saw, 10 is the magic number—the encryption key. It is what we use to change each letter.

But it’s not very safe to use a simple number like this for encryption. Users could try 1 and then 2 and then 3 and eventually guess your code. To make this safer, you’d want to use different keys.

For example, what if the key was 314159? To encrypt HELLO, we’d use 3 for the H, 1 for the E, 4 for the first L, 1 for the second L, and 5 for the O. So HELLO becomes KFPMT. Guessing this is much harder because a different key is used for each letter.

But what if your text is longer than the key? Say you were encrypting the text Hello World. The key has 6 digits in it, we need 11 digits (because the space value has an ASCII value, too). What to do?

The answer is reuse the key. If the key is 6 digits long, we use these 6 digits for the first 6 letters to be encrypted. And then we start over: For letter 7, we use the first digit in the key, for letter 8 the second, and keep reusing the key over and over as needed.

How do we figure out which digit to use? We use division and look at the remainder. For the 8th character (we need the second number from the key), we just divide the character index (8, as this is the 8th character) by the key length (6), and the remainder is 2. In Chapter 3 we introduced the modulus operator (%), which finds a remainder. It is used like this:

print(8%6)

Here 8 is divided by 6, and the remainder is 2.

Using modulus, we can always divide the character position (8 for the 8th character, 42 for the 42nd, and so on) by the key length, and the remainder will point to a valid key digit to use.

Encryption Code

Ok, here’s the code for file Encrypt.py:

# ASCII range of usable characters - anything out of this range
could throw errors
asciiMin = 32   # Represents the space character - " "
asciiMax = 126  # Represents the tilde character - "~"

# Secret key
key = 314159    # Top secret! This is the encryption key!
key = str(key)  # Convert to string so can access individual digits

# Get input message
message = input("Enter message to be encrypted: ")

# Initialize variable for encrypted message
messEncr = ""

# Loop through message
for index in range(0, len(message)):
    # Get the ASCII value for this character
    char = ord(message[index])
    # Is this character out of range?
    if char < asciiMin or char > asciiMax:
        # Yes, not safe to encrypt, leave as is
        messEncr += message[index]
    else:
        # Safe to encrypt this character
        # Encrypt and shift the value as per the key
        ascNum = ord(message[index]) + int(key[index % len(key)])
        # If shifted past range, cycle back to the beginning of the range
        if ascNum > asciiMax:
            ascNum -= (asciiMax - asciiMin)
        # Convert to a character and add to output
        messEncr = messEncr + chr(ascNum)

# Display result
print("Encrypted message:", messEncr)

Save and run this code. It will ask you for a message to encrypt and will then display the encrypted message. It doesn’t decrypt; we’ll get to that next.

So how does this work?

Not all ASCII characters print well, so to be safe, you define the range of characters you want to use, like this:

asciiMin = 32   # Represents the space character - " "
asciiMax = 126  # Represents the tilde character - "~"

Next comes the key:

# Secret key
key = 314159    # Top secret! This is the encryption key!
key = str(key)  # Convert to string so can access individual digits

key is the numeric encryption key. This one has six digits, though yours can be longer or shorter. The key here (hee hee, bad pun, sorry) is that you must have the same key to encrypt and decrypt the text.

We need to use each digit in the key individually. Remember? That’s how we can encrypt each character with a different key digit. To do this, we convert the key to a string, so 314159 becomes "314159" because getting characters from a string is super easy. It’s much like getting them from a list. Remember those?

Next, the code asks for the text to be encrypted using an input() function. You’re very familiar with this one.

Then this code is used:

messEncr = ""

This creates an empty string variable named messEncr (for message encrypted). The code is going to encrypt the text one character at a time, and as it does so, the encrypted character will be added to this variable.

Now we loop through message:

for index in range(0, len(message)):

Here we use a for loop that loops from 0 to the length of the text. How does the loop know how long the text is? Once again, we can use the len() function for that. If the text is 10 characters long, len() returns 10, so the range for the loop will be range(0, 10), meaning loop from 0 to 9, exactly what we need. Within each iteration, the variable index will contain the iteration number: 0 the first time, 1 the second, and so on.

The code indented under the for statement will be executed once per character to be encrypted. At the start of each loop, we need to get the ASCII value for the letter being processed, like this:

char = ord(message[index])

message[index] lets us access a single character. index is 0 on the first loop, so on the first iteration, message[index] will return the first character. On the next iteration, it will return the second. ord() gets the ASCII code for the number, and that code is saved to the variable char.

The next if statement checks to see if the ASCII code for this character is within the safe range. If not, we don’t encode it. If yes, this code gets executed:

ascNum = char + int(key[index % len(key)])

This code does the actual encryption. index is the current character number (set by the for loop). index % len(key) divides the index by the length of the key, giving us a remainder, which is the key digit to use. That gets added to char (the current ASCII code), and the result is saved in ascNum. So if, for example, the loop is currently at index 9, and the key has six digits, index % len(key) will become 9 % 6, which returns 3 (the remainder), and index 3 of the key will be used.

The encoded character then gets appended to messEncr, like this:

messEncr = messEncr + chr(ascNum)

chr(ascNum) converts the newly calculated encoded character to a string, and that gets appended to messEncr. (Remember that adding strings concatenates them.)

As we mentioned previously, some ASCII characters don’t print, and so we need to make sure to exclude these. This code checks to ensure that the encoded character is within the safe range, and if it isn’t, it shifts to a safer value:

if ascNum > asciiMax:
    ascNum -= (asciiMax - asciiMin)

And finally, a print() is used to display the encrypted text.

That does it. Enter text, and the program will encrypt it using the digits in the secret key. If you send someone a message, they’d need the matching key to read it. You can use different keys for different people (that way they won’t be able to read each other’s messages).

Decryption Code

Great. But how do you decrypt the encrypted messages? The process is actually exactly the same. It’s so similar, in fact, that you can use the same Encrypt.py file and just make a few changes.

Image Tip

Use Save As If you use the VS Code Save As option (in the File menu) to save Encrypt.py as Decrypt.py, you have two files that are identical. Try it!

Click on Decrypt.py and we’ll make a few changes. First, change the input() so the prompt is correct:

message = input("Enter message to be decrypted: ")

Next, find the line that does the actual encryption, which looks like this:

ascNum = char + int(key[index % len(key)])

Recall that when we encrypt, we add a key digit. To decrypt, all we need to do is subtract the same digit. So, change the code to this:

ascNum = char - int(key[index % len(key)])

The + is changed to a -.

Next, look at the if statement right below the line you just edited. It checks to see that we haven’t gone above the allowed range and subtracts the change, if needed. We need to reverse that so it looks like this:

if ascNum < asciiMin:
    ascNum += (asciiMax - asciiMin)

In the if statement, the > gets changed to a <, and in the assignment, the -= becomes +=. Now if the decoding process creates a number below the range, we can fix that.

Some of the comments will be wrong, best to fix those, too.

That’s it! Now you can encrypt and decrypt messages, so long as both parties have the same key. And all made possible by some simple for loops.

Oh, using the key 314159, were you able to decrypt the encrypted text we showed you earlier in this chapter?

Summary

In this chapter, you learned how to use for loops to loop over a known set of items. In the next chapter, we’ll look at conditional loops (and how to use both loop types together).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset