A folded note lands on your desk:
nvvg nv zg gsv oryizibIt looks like nonsense, but your friend promises it hides a real message. Today we crack it by taking text apart, flipping it, and rebuilding it one character at a time.
Each tool comes as a short ladder — simplest first, then one step up, then the real cipher — with a Notice naming the one new thing each time.
-
Pick one character by its position, and a run of characters with a slice.
-
Clean text with
.lower(),.upper(),.strip()and.replace(). -
Test whether a character appears in a string with
in. -
Build a new string in a loop to encode and decode a secret message.
-
Lesson 1: Take the Note Apart
A string is text stored in order. Each character has a numbered position, starting at 0. Square brackets select one character by its position.
word = "nvvg" print(word[0])Scroll the code for more ↓
Output
nPositions start at 0, so
word[0]is the FIRST character.word = "nvvg" print(word[1]) print(word[2])Scroll the code for more ↓
Output
v vEach number picks the character sitting at that position.
word = "nvvg" print(word[-1])Scroll the code for more ↓
Output
gA negative position counts back from the end —
[-1]is the last character.word = "code" for position in range(4): print(word[position])Scroll the code for more ↓
Output
c o d eA loop can walk every position in order, one character at a time.
A slice selects a RUN of characters. In
text[start:stop]Python includesstartand stops just beforestop.word = "nvvg" print(word[1:4])Scroll the code for more ↓
Output
vvg[1:4]takes positions 1, 2, 3 — up to but NOT including 4.word = "nvvg" print(word[:2])Scroll the code for more ↓
Output
nvLeave the start blank to begin at position 0.
word = "nvvg" print(word[2:])Scroll the code for more ↓
Output
vgLeave the stop blank to run all the way to the end.
word = "noon ta teem" print(word[::-1])Scroll the code for more ↓
Output
meet at noon[::-1]steps backward, reversing the whole string — a quick reverse cipher.Messages often arrive with extra spaces, mixed case, or separator marks. String methods make a CHANGED copy (the original stays the same). This unit uses four:
.lower(),.upper(),.strip(),.replace(old, new).note = "MEET ME" print(note.lower()) print(note)Scroll the code for more ↓
Output
meet me MEET ME.lower()returns a lowercase copy;noteitself is unchanged.quiet = "meet me" print(quiet.upper())Scroll the code for more ↓
Output
MEET ME.upper()returns an uppercase copy.padded = " meet me " print(padded.strip())Scroll the code for more ↓
Output
meet me.strip()removes the spaces from both ends.coded = "meet_me_at_noon" print(coded.replace("_", " "))Scroll the code for more ↓
Output
meet me at noon.replace(old, new)swaps everyoldpiece fornew.messy = " MEET_ME_AT_NOON " clean = messy.strip().lower().replace("_", " ") print(clean)Scroll the code for more ↓
Output
meet me at noonMethods can be CHAINED — each one works on the result of the one before it.
Lesson 2: Decode the Folded Note
Our note uses a swap: the first alphabet letter trades with the last, the second with the second-to-last, and so on. The loop checks all 26 positions to find each coded letter; a letter at
positionhas its partner at25 - position. Characters not found (a space or mark) are copied unchanged.letters = "abcdefghijklmnopqrstuvwxyz" coded_note = "nvvg nv zg gsv oryizib" coded_note = coded_note.lower() decoded_note = "" for letter in coded_note: new_letter = letter for position in range(26): if letters[position] == letter: new_letter = letters[25 - position] decoded_note = decoded_note + new_letter print(decoded_note)Scroll the code for more ↓
Output
meet me at the libraryThe note says meet me at the library. Trace one coded character through the inner loop: which position is found, and which partner is used? Why does the accumulator begin as an empty string?
Try it coded_note = input("Type the coded note: ") letters = "abcdefghijklmnopqrstuvwxyz" coded_note = coded_note.lower() decoded_note = "" for letter in coded_note: new_letter = letter for position in range(26): if letters[position] == letter: new_letter = letters[25 - position] decoded_note = decoded_note + new_letter print(decoded_note)Scroll the code for more ↓
(It reads live input, so it does not run here.)
To WRITE a cipher we must tell letters from marks. The
inoperator asks whether one value appears inside another.letters = "abcdefghijklmnopqrstuvwxyz" print("m" in letters)Scroll the code for more ↓
Output
Trueinreports True/False — is"m"somewhere inletters? Yes.letters = "abcdefghijklmnopqrstuvwxyz" print(" " in letters) print("!" in letters)Scroll the code for more ↓
Output
False FalseA space and a
!are not letters, soinis False for them.letters = "abcdefghijklmnopqrstuvwxyz" message = "hi!" for character in message: if character in letters: print(character + " is a letter") else: print(character + " is a mark")Scroll the code for more ↓
Output
h is a letter i is a letter ! is a markInside a loop,
intests each character — here it just labels them; the real cipher will use that test to shift letters and copy marks unchanged.Lesson 3: Write a Code Your Friend Can Crack
Now we shift each letter forward, leaving spaces and punctuation untouched. Here is a deliberate bug: a late letter can step past the end of the alphabet. Predict which line the traceback points to when the message starts with
z.Error demo: this code stops with an error def encode_without_wrap(message, shift): letters = "abcdefghijklmnopqrstuvwxyz" message = message.lower() result = "" for letter in message: if letter in letters: for position in range(26): if letters[position] == letter: new_position = position + shift result = result + letters[new_position] else: result = result + letter return result print(encode_without_wrap("zoo", 3))Scroll the code for more ↓
Read a traceback from the bottom:
IndexErrormeans a position fell outside the string. The remainder operator repairs the wrap —(position + shift) % 26is always 0 through 25.def encode(message, shift): letters = "abcdefghijklmnopqrstuvwxyz" message = message.lower() result = "" for letter in message: if letter in letters: for position in range(26): if letters[position] == letter: new_position = (position + shift) % 26 result = result + letters[new_position] else: result = result + letter return result coded_message = encode("Meet me at 4!", 3) print(coded_message)Scroll the code for more ↓
Output
phhw ph dw 4!Trace the first
m: the scan finds position 12, adds shift 3, selects position 15 —p. Forz, 25 + 3 = 28, and28 % 26wraps to 2 —c. Theresultvariable is a string accumulator, built left to right.def decode(message, shift): return encode(message, 26 - shift) original_message = "meet me at 4!" coded_message = encode(original_message, 3) decoded_message = decode(coded_message, 3) print(f"Coded: {coded_message}") print(f"Decoded: {decoded_message}")Scroll the code for more ↓
Output
Coded: phhw ph dw 4! Decoded: meet me at 4!Encoding then decoding is a round trip. Because the encoder lowercases first, the decoded text matches the lowercase original; spaces, the number, and
!survive unchanged.Try it message = input("Type a secret message: ") shift = 3 coded_message = encode(message, shift) print(coded_message)Scroll the code for more ↓
(It reads live input, so it does not run here.)
Trade coded messages with a partner and share the shift separately. Predict how spaces and punctuation will look, then explain why both friends need the same shift.
Algorithm Extension
The algorithm-pattern Spotlights for this unit, gathered here for reference — read them alongside the Algorithm Extension exercises.
Pattern Spotlight: Scan until found + stop early
Kid hook: check items one by one;
breakwhen found = stop working early. A person may spot a letter at a glance, but the program checks positions in order. After the comparison finds the letter,breakavoids checking the rest. (The scan in the lesson above checks all 26 positions; Exercise 14 adds thebreakso it can stop early.)print("a" in "aeiou")Scroll the code for more ↓
Output
Trueintests ONE character against the vowels — 'a' is a vowel.word = "cat" for ch in word: print(ch in "aeiou")Scroll the code for more ↓
Output
False True FalseRun the same
intest on each character in turn —catgivesFalse,True,False.word = "cat" position = 0 for ch in word: print(position) position = position + 1Scroll the code for more ↓
Output
0 1 2A
positioncounter, stepped once per character, tracks WHERE we are:0,1,2.word = "cat" position = 0 found = -1 for ch in word: if ch in "aeiou": found = position position = position + 1 print(found)Scroll the code for more ↓
Output
1Instead of printing every step, remember the index of the vowel in
found(a-1stand-in for "none yet") — the vowel ofcatis at index1. Without abreakthis keeps the LATEST match; R5'sbreakwill keep the FIRST.word = "cat" position = 0 found = -1 for ch in word: if ch in "aeiou": found = position break position = position + 1 print(found)Scroll the code for more ↓
Output
1breakSTOPS at the FIRST vowel instead of scanning the rest — same index1, less work.word = "myth" position = 0 found = -1 for ch in word: if ch in "aeiou": found = position break position = position + 1 print(found)Scroll the code for more ↓
Output
-1mythhas no vowel, so the loop falls through and the-1stand-in reports "not found".Pattern Spotlight: Do the same to each (map)
To apply the same step to every item, producing a new sequence, loop over the message one character at a time and add each transformed character to a new string. A whole-string method changes the message in one call; an explicit per-character loop builds a transformed result for every character, unlike counting only characters that meet a condition.
result = "" result = result + "h".upper() print(result)Scroll the code for more ↓
Output
HTransform one character with
.upper()and add it to the new string —"h"becomes"H".result = "" result = result + "h".upper() result = result + "i".upper() print(result)Scroll the code for more ↓
Output
HIThe same step applied again grows the transformed string to
"HI".word = "hi" result = "" for ch in word: result = result + ch.upper() print(result)Scroll the code for more ↓
Output
HIThe loop applies the same
.upper()transform to EVERY character of"hi", building"HI".-
Positions start at 0, and
word[-1]is the last character. -
text[start:stop]includesstartand stops just beforestop;[::-1]reverses. -
A string method returns a changed copy, and the original stays the same.
-
inasks whether one value appears inside another. -
(position + shift) % 26wraps pastz, and decoding shifts by26 - shift.
You can now write a secret code a friend can crack.
-