LambdaLabTM
Computer Science · Class 11 · Strings Revisited
ProgramsCiphers⏱️ 17 min read

Alphabets & Secret Messages

Now the codes earn their keep. Because the alphabet is 26 consecutive numbers, a letter can be counted to, added to and moved along — and the last program on this page, the Caesar cipher, is the one where every idea in this chapter arrives at once: traversal, ord() and chr(), a range check, and % to wrap Z round to A.

1Program 1 — build the alphabet

📋 The problem

Print the capital letters A to Z on one line, without typing them out.

alphabet.py
# the alphabet, built from the codes

letters = ''

for code in range(65, 91):
    letters = letters + chr(code)

print(letters)
Output
ABCDEFGHIJKLMNOPQRSTUVWXYZ
for code in range(65, 91):

The loop counts NUMBERS, not characters — this is a range loop like any other, and the string only appears when chr() is called. 91 as the stop, because 90 is Z and range() leaves its stop out.

letters = letters + chr(code)

The collector from every building program on this page, fed by chr() instead of by a character taken from another string.

Tip
Why build a string instead of printing each letter? print() moves to a new line every time, so printing inside the loop gives a vertical column of 26 letters. The row is collected and printed once — the same reason the triangle patterns build line before printing it.

Backwards is the same loop with the range() turned round, and a space added after each letter so the output can be read:

alphabet_back.py
# the alphabet backwards, with a space between the letters

letters = ''

for code in range(90, 64, -1):
    letters = letters + chr(code) + ' '

print(letters)
Output
Z Y X W V U T S R Q P O N M L K J I H G F E D C B A
Watch Out
The stop is 64, one step past 65. Going down, one step past the last value you want is one below it. Writing 65 as the stop drops the A — and it is the same rule as the backwards triangles: the stop is one step past the last value, whichever way the loop is going.

2Program 2 — the total of a word's codes

📋 The problem

Add up the code numbers of every character in a word.

code_total.py
# the total of the codes of a word

word = input('Enter a word: ')
total = 0

for ch in word:
    total = total + ord(ch)

print('The codes of', word, 'add up to', total)
Output
Enter a word: CAT
The codes of CAT add up to 216

67 + 65 + 84 = 216. A pointless-looking program that is set constantly, because it is the shortest thing that proves you can turn a character into a number and accumulate it. It is also the seed of a real idea: adding up a string's codes is the crudest possible checksum — change one letter and the total changes.

Watch Out
ord() goes inside the loop, not around the word. ord(word) raises TypeError: ord() expected a character, but string of length 3 found. One character at a time is the only way it works.

3Program 3 — the letter n places along

📋 The problem

Ask for a capital letter and a number, and print the letter that many places further along the alphabet — wrapping round from Z back to A.

jump_letter.py
# the letter that comes n places after another one

letter = input('Enter a capital letter: ')
jump = int(input('How many places along? '))

place = ord(letter) - 65
place = (place + jump) % 26

print(letter, 'plus', jump, 'places is', chr(place + 65))
Output
Enter a capital letter: Z
How many places along? 3
Z plus 3 places is C
place = ord(letter) - 65

Turn the code into a place in the alphabet: A becomes 0, B becomes 1, Z becomes 25. Everything is easier in 0–25 than in 65–90, and this is the line that gets you there.

place = (place + jump) % 26

Move along, then wrap. % 26 is the whole trick: 25 + 3 is 28, and 28 % 26 is 2 — which is C. Without it, Z + 3 would give 93, a character that is not a letter at all.

chr(place + 65)

Back the other way: add 65 to return from a place in the alphabet to a real code, then chr() for the character. Every cipher program is these three lines with something in the middle.

Key Takeaway
Subtract 65, do the work, add 65 back. That sandwich is worth learning as a shape. Inside it you are working with plain numbers 0 to 25, where % 26 means “wrap round the alphabet”. Try to do the arithmetic on the raw codes and the wrapping becomes a mess of ifs.

4Program 4 — the Caesar cipher

📋 The problem

Shift every letter of a message three places along the alphabet, leaving spaces and punctuation alone. Capitals stay capitals and small letters stay small.

encrypt.py
# shift every letter three places along the alphabet

message = input('Enter a message: ')
shift = 3
secret = ''

for ch in message:
    if ch.isupper():
        place = ord(ch) - 65
        place = (place + shift) % 26
        secret = secret + chr(place + 65)
    elif ch.islower():
        place = ord(ch) - 97
        place = (place + shift) % 26
        secret = secret + chr(place + 97)
    else:
        secret = secret + ch

print('Secret message:', secret)
Output
Enter a message: Attack at Dawn!
Secret message: Dwwdfn dw Gdzq!

Three branches, and you have seen all three before. The capitals use 65 as their base and the small letters use 97 — the same sandwich twice, with a different number in it. The else keeps the space and the exclamation mark, which is why the secret message is still readable as a sentence.

Watch Out
The else matters more than it looks. Without it, every space and every mark is dropped and Attack at Dawn! encrypts to DwwdfndwGdzq — which cannot be decrypted back to the original, because the information about where the words ended has been thrown away.

5Program 5 — decrypt it

📋 The problem

Take an encrypted message and shift it back.

decrypt.py
# and shift them back again

secret = input('Enter the secret message: ')
shift = 3
message = ''

for ch in secret:
    if ch.isupper():
        place = ord(ch) - 65
        place = (place - shift) % 26
        message = message + chr(place + 65)
    elif ch.islower():
        place = ord(ch) - 97
        place = (place - shift) % 26
        message = message + chr(place + 97)
    else:
        message = message + ch

print('Message:', message)
Output
Enter the secret message: Dwwdfn dw Gdzq!
Message: Attack at Dawn!
Key Takeaway
One character changed: + shift became - shift. And % does the wrapping in both directions, because Python works out (0 - 3) % 26 as 23 — a positive answer, which is exactly what is needed to land back on X. In many other languages that expression is negative and the program breaks; Python is being unusually helpful here, and it is worth knowing that.
wrap_check.py
# what % does at the two ends

print((25 + 3) % 26)
print((0 - 3) % 26)
Output
2
23
encrypt.py

6Program 6 — prove it works

📋 The problem

Encrypt a message and decrypt the result in one program, and check that what comes out is what went in.

round_trip.py
# encrypt, then decrypt, and compare with the original

message = 'Meet me at Ten'
shift = 5
secret = ''

for ch in message:
    if ch.isupper():
        secret = secret + chr((ord(ch) - 65 + shift) % 26 + 65)
    elif ch.islower():
        secret = secret + chr((ord(ch) - 97 + shift) % 26 + 97)
    else:
        secret = secret + ch

back = ''

for ch in secret:
    if ch.isupper():
        back = back + chr((ord(ch) - 65 - shift) % 26 + 65)
    elif ch.islower():
        back = back + chr((ord(ch) - 97 - shift) % 26 + 97)
    else:
        back = back + ch

print('Original: ', message)
print('Encrypted:', secret)
print('Decrypted:', back)
print('Same as the original?', back == message)
Output
Original:  Meet me at Ten
Encrypted: Rjjy rj fy Yjs
Decrypted: Meet me at Ten
Same as the original? True

The sandwich is written on one line here — chr((ord(ch) - 65 + shift) % 26 + 65) — which is the same four steps with the brackets doing the ordering. Read it from the inside out: ord(ch), take 65, shift and wrap, add 65, chr(). Write it in three lines while you are learning it, and in one when you can see all four steps at a glance.

Key Takeaway
The last line is the program checking its own work. back == message printing True is worth more than any amount of eyeballing the middle line, and it costs one print(). Any program that transforms something and can transform it back should end this way while you are testing it.
round_trip.py — the same program with shift = 13, the only line changed
Output
Original:  Meet me at Ten
Encrypted: Zrrg zr ng Gra
Decrypted: Meet me at Ten
Same as the original? True

7Recap

chr() turns a counting loop into letters

range(65, 91) counts numbers; chr(code) makes each one a character. The alphabet needs no typing out.

Subtract 65, work in 0–25, add 65 back

The sandwich. Every cipher program is those three steps with something in the middle, and 97 in place of 65 for small letters.

% 26 is what wraps Z round to A

(25 + 3) % 26 is 2. And (0 - 3) % 26 is 23 in Python, so the same expression decrypts as well as it encrypts.

The else keeps the message readable

Spaces and punctuation must be added unchanged, or the words run together and the message cannot be read back.

✍️ Now write these yourself
  1. 1

    Print the small-letter alphabet, and then every second letter of it.

    Hint · range(97, 123), and a step of 2 for the second part.

  2. 2

    Print each letter of the alphabet beside its code, five to a line.

    Hint · Build a line, and print it whenever the count so far divides by 5.

  3. 3

    Let the user choose the shift, instead of fixing it at 3.

    Hint · One int(input()) — the rest of the program does not change at all, which is the point of having named it shift.

  4. 4

    Encrypt only the vowels of a message, leaving the consonants alone.

    Hint · One more test inside the letter branches. Note it is no longer reversible unless the shifted vowel is still a vowel — think about why.

  5. 5

    Write the reverse-alphabet cipher: A becomes Z, B becomes Y, and so on.

    Hint · Inside the sandwich, the new place is 25 - place. No % needed — it cannot go out of range.

Quick Check

Why does the Caesar cipher use % 26 rather than just adding the shift?

Quick Check

In Python, what is (0 - 3) % 26?

Quick Check

An encryption program has no else branch. What happens to 'Attack at Dawn!'?