3.04 Strings
An in-depth, three-cycle lesson on Python strings — indexing, slicing, methods, and formatting — framed as a signal-interception mission.
1. Reference Guide
Key Terms: Mutable vs. Immutable
| Term | Definition | Examples |
|---|---|---|
| Mutable | An object whose contents can be changed in place after it’s created — no new object is made, the same one is edited. | list, dict, set |
| Immutable | An object whose contents cannot be changed in place. Any “edit” builds a brand-new object in memory and leaves the original untouched. | str, tuple, int, float |
A string is an ordered, immutable sequence of characters. You never edit a string in place — every “change” actually builds a brand-new string and throws the old one away.
Interceptor Skill Reference
| Skill | What it lets an interceptor do |
|---|---|
| Indexing & slicing | Pull out exact characters or chunks of a message by position |
| String methods | Clean up noisy text, search for keywords, split and rejoin fragments |
| Concatenation & formatting | Rebuild a decoded message and produce a readable report |
2. LxD Cycle Process
Empathize: Students often think strings behave like lists they can edit directly, get tripped up by zero-indexing and negative indices, and forget that .upper(), .replace(), and every other string method return a brand-new string instead of changing the original.
Define:
- POV: CSP students need a concrete, hands-on way to feel why strings are ordered and immutable, not just memorize indexing rules from a slide.
- Learning Goal: Students will index and slice strings to extract characters and substrings, apply built-in string methods to clean and search text, and use concatenation and formatting to build new strings out of pieces.
Ideate:
- HMW Question: How might we make abstract indexing and slicing rules feel like a real, useful skill instead of trivia to memorize?
- Activity: Three short cycles of a tech talk followed by a code runner exercise, framed as decoding intercepted transmissions, ending in a combined homework mission.
Prototype & Test: A trial run with a professor reviewer surfaced two gaps in the first draft — see Section 8 for what was found and how it was fixed.
3. College Board Requirements
AP CSP Big Idea 3: Algorithms and Programming (AAP), Topic 3.4 Strings. Quoted from the course and exam description (College Board, 2023):
- AAP-1.C.4 “A string is an ordered sequence of characters.”
- AAP-2.D.1 “String concatenation joins together two or more strings end-to-end to make a new string.”
- AAP-2.D.2 “A substring is part of an existing string.”
Cycle 1 (indexing & slicing) builds the “ordered sequence” idea from AAP-1.C.4 and the substring concept from AAP-2.D.2. Cycle 3 (concatenation & formatting) is a direct application of AAP-2.D.1. Cycle 2 (string methods) extends both essential knowledge statements into the built-in operations Python provides for cleaning and searching text.
4. Lesson Plan
Learning Objective: By the end of this lesson, you will be able to index and slice strings to extract specific characters or substrings, apply string methods to clean, search, and transform text, and use concatenation and f-strings to build new strings from smaller pieces.
Success Criteria: You can predict the exact output of an indexing or slicing expression, choose the correct string method for a cleaning or searching task, and combine slicing, methods, and formatting to solve a multi-step string problem.
| Cycle | Tech Talk (1–2 min) | Popcorn Hack (5 min) |
|---|---|---|
| 1 | Indexing & slicing | Extract a hidden message by position |
| 2 | String methods (clean & search) | Scrub and search a noisy transmission |
| 3 | Concatenation & formatting | Rebuild a decoded report |
| Homework | Crack the Final Transmission |
5. Code Examples
A. Quote Styles
A string is Python’s way of representing text: any sequence of characters — letters, numbers, symbols, even spaces — wrapped in quotes so Python knows it’s data, not code.
You can create a string with single quotes, double quotes, or triple quotes (for text that spans multiple lines):
Code Runner Challenge
Demo - Create strings with different quote styles
View IPYNB Source
message = "Hello"
also_a_string = 'Hello'
multiline = """Hello,
world!"""
Single and double quotes work the same — pick whichever lets you avoid escaping a quote inside the string ("it's a valid spec" is easier to read than 'it\'s a valid spec').
Run the demo below to see the different quote styles in action.
Code Runner Challenge
Demo - Try to edit a string in place and watch it fail
View IPYNB Source
# CODE_RUNNER: Demo - Create strings with different quote styles
single = 'Auto Racing'
double = "Auto Racing"
multiline = """SPEC 1.1
FLYWHEEL"""
print(single == double) # True -> the quote style you pick doesn't change the value
print(type(single)) # <class 'str'> -> both quote styles make the same type
print(len(single)) # ordered sequence -> it has a length, character by character
print(multiline) # triple quotes preserve the line break inside the string
B. Immutability
Once created, a string is ordered and immutable — see the Key Terms table in the Reference Guide above for the exact definitions. You never edit a string in place — every “change” actually builds a brand-new string and throws the old one away. Run the demo below and watch it fail on purpose.
Code Runner Challenge
Demo - Build a new string instead of editing in place
View IPYNB Source
# CODE_RUNNER: Demo - Try to edit a string in place and watch it fail
message = "HELLO"
message[0] = "J" # TypeError: 'str' object does not support item assignment
That’s the error you’ll hit every time you try to change a character in place. The fix is to build a brand-new string instead:
Code Runner Challenge
Demo - Index and slice an SFI record string
View IPYNB Source
# CODE_RUNNER: Demo - Build a new string instead of editing in place
message = "HELLO"
fixed = "J" + message[1:] # slice off the "H", then glue "J" onto the front
print(fixed)
6. Hacks & Practice Tasks
You are a signal interceptor. Every transmission that crosses your desk is just a Python string. Across the three cycles below you’ll use the skills from the Reference Guide to read, clean, search, and rebuild intercepted text — then combine all three skills to crack one final transmission for homework.
Prepare your submission IPYNB
Complete this quick-start flow so you can begin in about 2 minutes.
- Create a new notebook in your portfolio homework area:
_notebooks/homework. - Add one raw cell at the top with the frontmatter below.
- Add a code cell for each Popcorn Hack and the Homework. Keep the
# CODE_RUNNER:comment as the first line of each code cell. - Run each cell and check the output before submitting.
---
layout: post
title: 3.4 Strings HW
categories: [Python]
lesson_language: Python
lesson_topic: Strings HW
lesson_part: interactive
lesson_type: lesson
permalink: /python/strings-intercepters-hw
author: githubID
---
Submission Safety Rules (Read First)
[!IMPORTANT] To avoid grading errors, follow these rules exactly:
- Keep the
# CODE_RUNNER:comment as the first line of each submission cell.- Your code must run in the code runner with no errors.
- This lesson needs no imports at all — indexing, slicing, and every string method used here are built into the
strtype.- Do not use
input(). The code runner cannot type answers for you.
Cycle 1 Tech Talk: Indexing & Slicing (2 minutes)
Every character in a string has a position, called an index, starting at 0 for the first character. Negative indices count backward from the end, starting at -1 for the last character.
Slicing grabs a whole chunk at once with string[start:stop:step]. The start index is included, the stop index is excluded, and step controls direction and skipping. Run the demo below, then try editing record and the slice ranges to see how the output changes.
Code Runner Challenge
Popcorn Hack 1 - Extract the hidden message using indexing and slicing
View IPYNB Source
# CODE_RUNNER: Demo - Index and slice an SFI record string
record = "RACING-SPEC-1.1"
print(record[0]) # 'R' -> first character, index 0
print(record[-1]) # '1' -> last character, index -1
print(record[7:11]) # 'SPEC' -> characters 7 up to (not including) 11
print(record[::2]) # every OTHER character, start to end
print(record[::-1]) # the whole string, reversed
print(len(record)) # 15 -> total number of characters
Python vs. College Board Pseudocode
The AP CSP Exam Reference Sheet indexes sequences starting at 1, not 0 — this is the single biggest gotcha when translating pseudocode into Python.
| Form | First character of record |
|---|---|
| Python | first = record[0] |
| College Board pseudocode | first ← record[1] |
College Board pseudocode has no negative-indexing shortcut like Python’s record[-1] — it only ever counts up from 1.
record[7:11]never includes index11— that’s the single most common slicing bug.record[a:b]always returns a new string;recorditself never changes.- If you leave out
startorstop, Python assumes “the beginning” or “the end.”
Tech Talk: Why Immutability Matters
Because strings can’t be changed in place, “editing” a message really means slicing out the parts you want to keep and gluing them back together with pieces you want to add. That’s the whole game for the rest of this lesson.
Popcorn Hack 1: Extract the Hidden Message (5 minutes)
Task: An intercepted transmission has a real message hidden inside decoy characters at fixed positions. Use only indexing and slicing — no string methods yet — to pull the real words out.
Code Runner Challenge
Demo - Clean and search a string with methods
View IPYNB Source
# CODE_RUNNER: Popcorn Hack 1 - Extract the hidden message using indexing and slicing
transmission = "xxSIGNALxxxCONFIRMEDxx77xxDELTA"
# TODO 1: Use slicing to pull out "SIGNAL" (it starts at index 2 and is 6 characters long)
word_1 = ""
print("Word 1:", word_1)
# TODO 2: Use slicing to pull out "CONFIRMED" (it starts right after "SIGNALxxx")
word_2 = ""
print("Word 2:", word_2)
# TODO 3: Use NEGATIVE indexing/slicing to pull out "DELTA" (it is the last 5 characters)
word_3 = ""
print("Word 3:", word_3)
# TODO 4: Use len() to print how many characters are in the FULL transmission
print("Total length:", 0)
# TODO 5: Use a slice with a step of 2 (transmission[::2]) to print every other character.
# Look closely: is any part of the hidden message still visible in this pattern?
print("Every other character:", "")
Cycle 2 Tech Talk: String Methods — Clean & Search (2 minutes)
A string method is a built-in procedure attached to every string with .method_name(). Just like slicing, every method below returns a new string (or a new list, or a True/False) — none of them ever change the original. Run the demo, then swap in your own noisy string.
Code Runner Challenge
Demo - Split real-world separators (CSV and Markdown table)
View IPYNB Source
# CODE_RUNNER: Demo - Clean and search a string with methods
raw = " SFI-BACKEND says: the SPEC is DUPLICATE!!! "
clean = raw.strip() # removes leading/trailing whitespace
print(clean)
print(clean.lower()) # a lowercase COPY
print(clean.replace("!", "")) # a copy with "!" removed
print(clean.split(":")) # splits into a LIST of pieces on ":"
print("duplicate" in clean.lower()) # case-insensitive search with `in`
print(clean.find("SPEC")) # index where "SPEC" starts, or -1 if missing
print(clean.count("S")) # how many times "S" appears
| Method | What it does |
|---|---|
.strip() |
Removes whitespace (or given characters) from both ends |
.upper() / .lower() |
Returns an all-caps / all-lowercase copy |
.replace(old, new) |
Returns a copy with every old swapped for new |
.split(sep) |
Breaks a string into a list of strings at every sep |
"x" in s |
True/False — is x found anywhere inside s? |
.find(sub) |
Index of the first match, or -1 if not found |
.count(sub) |
How many non-overlapping times sub appears |
Watch out: .replace() and .strip() don’t change raw — they hand back a new string that you must save into a variable, or the cleanup is lost.
Real-World Separators: CSV and Markdown Tables
Custom markers like ## don’t show up in the real world. Real records almost always use standard separators:
- CSV (comma-separated values) — the standard format for spreadsheets and data exports:
"Auto Racing,1.1,Replacement Flywheels,2001-11-09" - Markdown tables — separate columns with a pipe
|:"Auto Racing | 1.1 | Replacement Flywheels | 2001-11-09"
.split() works exactly the same either way — you just pass in the character the format actually uses.
Code Runner Challenge
Popcorn Hack 2 - Clean and search a noisy transmission using string methods
View IPYNB Source
# CODE_RUNNER: Demo - Split real-world separators (CSV and Markdown table)
csv_line = "Auto Racing,1.1,Replacement Flywheels,2001-11-09"
fields = csv_line.split(",") # comma -> CSV
print(fields)
table_row = "Auto Racing | 1.1 | Replacement Flywheels | 2001-11-09"
columns = [piece.strip() for piece in table_row.split("|")] # pipe -> Markdown table
print(columns)
Popcorn Hack 2: Scrub and Search a Noisy Transmission (5 minutes)
Task: This transmission is full of noise — stray punctuation, extra whitespace, and mismatched case. Clean it up and search it using only string methods.
Code Runner Challenge
Demo - Concatenation, str(), f-strings, and join
View IPYNB Source
# CODE_RUNNER: Popcorn Hack 2 - Clean and search a noisy transmission using string methods
noisy = " ...AGENT>>>Falcon---reports: the PACKAGE has ARRIVED!!! "
# TODO 1: Use .strip() to remove the leading/trailing whitespace. Save it as trimmed.
trimmed = noisy
print("Trimmed:", repr(trimmed))
# TODO 2: Use .replace() (you may need it more than once) to remove every ">", "-", and "!" character.
# Save the result as cleaned.
cleaned = trimmed
print("Cleaned:", cleaned)
# TODO 3: Use .lower() to make an all-lowercase COPY of cleaned. Save it as lowered.
lowered = cleaned
print("Lowered:", lowered)
# TODO 4: Use the `in` operator on lowered to check whether "arrived" appears anywhere in the message.
has_arrived = False
print("Contains 'arrived':", has_arrived)
# TODO 5: Use .split() with no arguments on cleaned to break it into a list of words.
# Save it as words, then print how many words there are with len().
words = []
print("Word count:", len(words))
Cycle 3 Tech Talk: Concatenation & Formatting (2 minutes)
Concatenation glues strings together with +. Python will not silently convert other types for you — mixing a string with an int or float using + raises a TypeError, so you must convert it first with str(). Run the demo below to see all three styles side by side.
Code Runner Challenge
Demo - Build a JSON-style line that mixes separators
View IPYNB Source
# CODE_RUNNER: Demo - Concatenation, str(), f-strings, and join
part = "Flywheel Assembly"
status = "validated"
count = 3
report = part + " status: " + status # + only works string-to-string
report_with_number = part + " matched " + str(count) + " specs" # str() converts the int first
print(report)
print(report_with_number)
# f-strings are the cleanest way to mix types - no manual + or str() needed
report_f = f"{part} status: {status} ({count} specs matched)"
print(report_f)
spec_numbers = ["1.1", "1.2", "2.1"]
print("-".join(spec_numbers)) # glues a LIST into ONE string, with "-" between pieces
Python vs. College Board Pseudocode
String concatenation with + and College Board pseudocode’s assignment arrow ← map onto each other directly:
| Form | Concatenate two strings |
|---|---|
| Python | report = part + " status: " + status |
| College Board pseudocode | report ← part + " status: " + status |
The ← arrow is how the AP CSP Exam Reference Sheet always writes assignment — Python’s = means exactly the same thing.
Three ways to build a combined string, from clunkiest to cleanest:
| Style | Example | Notes |
|---|---|---|
+ concatenation |
"Score: " + str(score) |
Every non-string value needs str() first |
.format() |
"Score: {}".format(score) |
No manual str() needed, but harder to read |
| f-string | f"Score: {score}" |
Cleanest — values are converted automatically |
"sep".join(list_of_strings) is the reverse of .split() — it’s the standard way to turn a list of pieces back into one string.
Bonus: JSON Mixes Separators Too
JSON (JavaScript Object Notation) is the standard format for sending structured data between programs. A single JSON object mixes several separators at once: { } wrap the whole thing, : separates a key from its value, and , separates one key-value pair from the next.
f-strings make it easy to build a JSON-style line by hand. One gotcha: JSON’s true/false are lowercase, unlike Python’s True/False — convert booleans with str(value).lower() before dropping them in.
Code Runner Challenge
Popcorn Hack 3 - Build a decoded report using concatenation and formatting
View IPYNB Source
# CODE_RUNNER: Demo - Build a JSON-style line that mixes separators
import json
part = "Flywheel Assembly"
confidence = 87
spec_numbers = ["1.1", "1.2", "2.1"]
duplicate = True
joined_specs = "-".join(spec_numbers) # turn the list into one dash-joined string first
json_line = json.dumps({
"part": part,
"confidence": confidence,
"specs": joined_specs,
"duplicate": duplicate,
})
print(json_line)
Popcorn Hack 3: Build a Decoded Report (5 minutes)
Task: Combine fragments recovered from the last two cycles into a single, formatted intercept report using concatenation, str(), f-strings, and .join().
Code Runner Challenge
Homework - Crack the Final Transmission using slicing, methods, and formatting
View IPYNB Source
# CODE_RUNNER: Popcorn Hack 3 - Build a decoded report using concatenation and formatting
agent_name = "Falcon"
target_word = "MIDNIGHT"
confidence = 87 # a percentage, stored as an int
fragments = ["ALPHA", "SEVEN", "DELTA"]
# TODO 1: Use + concatenation to build: "Agent Falcon intercepted: MIDNIGHT"
headline = ""
print(headline)
# TODO 2: confidence is an int. Use str() and + concatenation to build: "Confidence: 87%"
confidence_line = ""
print(confidence_line)
# TODO 3: Rewrite the SAME line as confidence_line_fstring, but using an f-string this time
confidence_line_fstring = ""
print(confidence_line_fstring)
# TODO 4: Use "-".join(fragments) to combine the fragments list into one string
joined_fragments = ""
print("Joined fragments:", joined_fragments)
# TODO 5: Use an f-string to build ONE final report line combining headline, confidence_line,
# and joined_fragments, in the form:
# "REPORT: <headline> | <confidence_line> | Fragments: <joined_fragments>"
final_report = ""
print(final_report)
Homework Hack: Crack the Final Transmission
Task: One transmission is left, and it needs all three skills — indexing/slicing, string methods, and formatting — to fully decode. This time the transmission arrives the way real systems actually send data: as a comma-separated (CSV-style) row. Your finished program must:
- Clean and split it. Use
.strip()to remove outer whitespace, then.split(",")to break the CSV row into fields — the same technique spreadsheets and data exports use. - Extract by index. Use indexing on the resulting list to pull out the date, the agent codename, the raw message code, and the confidence score.
- Slice-check the date. Use slicing (not a method) to confirm the date field starts with the year
"2026". - Clean and search the message. Use
.replace()to turn the dashes in the message code into spaces, then use.lower()and theinoperator to check whether the word"dawn"appears anywhere in it. - Rebuild it as a Markdown table row. Use an f-string with
|separators to turn the decoded fields into one Markdown table row:| <date> | <agent> | <message> | <confidence>% |. - Rebuild it as a JSON line. Use an f-string to build a JSON-style line for the same data — mixing
{ },:, and,the way real systems pass structured data between programs. Remember: JSON booleans are lowercase (true/false), not Python’sTrue/False.
# CODE_RUNNER: Homework - Crack the Final Transmission using slicing, methods, and formatting
raw_transmission = " 2026-09-15,GHOST,THE-EAGLE-LANDS-AT-DAWN,87 "
# TODO 1: Use .strip() to remove the outer whitespace, then .split(",") to break it into fields.
# Print segments. Since this is a real CSV row (no leading/trailing marker), there
# should be exactly 4 fields and no stray empty strings.
segments = []
print("Segments:", segments)
# TODO 2: Use indexing on segments to pull out the date, agent, message code, and confidence.
date = ""
agent = ""
message_code = ""
confidence_str = ""
print("Date:", date, "| Agent:", agent, "| Message code:", message_code, "| Confidence:", confidence_str)
# TODO 3: Use SLICING (not a method) on date to confirm it starts with the year "2026".
year_check = ""
print("Year check:", year_check)
# TODO 4: Use .replace() to turn the dashes in message_code into spaces. Save it as message.
message = message_code
# TODO 5: Use .lower() and the `in` operator to check whether "dawn" appears anywhere in message.
found_dawn = False
print("Found 'dawn':", found_dawn)
# TODO 6: Use an f-string with "|" separators to build a Markdown table row of the decoded fields,
# in the form:
# "| <date> | <agent> | <message> | <confidence_str>% |"
markdown_row = ""
print("Markdown row:", markdown_row)
# TODO 7: Use an f-string to build a JSON-style line for the same fields, mixing "{", "}", ":", and ","
# the way real systems pass structured data. Use str(found_dawn).lower() for the boolean, since
# JSON uses lowercase true/false instead of Python's True/False. Form:
# {"date": "<date>", "agent": "<agent>", "message": "<message>", "confidence": <confidence_str>, "contains_dawn": <found_dawn lowercase>}
json_line = ""
print("JSON line:", json_line)
7. Grading Plan (1 Point Total)
Classroom Rubric
| Activity | Points | What earns the points |
|---|---|---|
| Popcorn Hacks (all 3) | 0.3 (0.1 each) | Student completed all TODOs in each Popcorn Hack and the cell runs without errors. |
| Homework — splitting | 0.15 | Correctly strips and splits the CSV transmission into fields on , — a real-world separator, not a made-up marker. |
| Homework — indexing | 0.15 | Correctly extracts the date, agent, message code, and confidence by index. |
| Homework — slicing | 0.1 | Correctly slices the date to confirm the year, without using a string method. |
| Homework — methods | 0.15 | Correctly replaces dashes with spaces and performs a case-insensitive search for “dawn”. |
| Homework — formatting | 0.15 | Builds a correct Markdown table row (pipe-separated) AND a JSON-style line (mixing { }, :, and ,) from the decoded fields. |
| Total | 1.0 |
Quick Validation Checklist
- Present:
# CODE_RUNNER:comment as the first line of each cell. - Present: at least one slicing expression that does NOT use a string method.
- Present: at least three different string methods (for example
.strip(),.split(),.replace(),.lower()). - Present: a Markdown-style table row (pipe-separated) and a JSON-style line (comma/colon/brace-separated) built from the decoded fields.
- Absent:
input(), anyimportstatement, and errors when run.
8. Lesson Revisions & Feedback Evidence
Feedback Received: A trial run with a professor reviewer surfaced two gaps in the first draft. First, the homework’s ## marker didn’t resemble any real-world data format, so decoding it didn’t transfer to a skill students would actually reuse elsewhere. Second, the mutable/immutable contrast landed well as an example but was never formally defined as a term, so students without the vocabulary had nothing to anchor it to.
Revision Made: The homework was rebuilt around CSV commas, Markdown-table pipes, and a JSON line — real-world separators instead of a made-up marker. An explicit Key Terms callout was added to the Reference Guide, now Section 1 at the top of the lesson, so students have the mutable/immutable vocabulary before they need it.
References
College Board. (2023). AP Computer Science Principles: Course and Exam Description (Fall 2023 ed.). Big Idea 3: Algorithms and Programming, Topic 3.4: Strings (AAP-1.C.4, AAP-2.D.1, AAP-2.D.2). New York, NY: College Board.
CompuScholar, Inc. AP CSP Learning Objectives and Essential Knowledge alignment [Cross-reference]. https://www.compuscholar.com/docs/apcsp/AP_CSP_Topic_Cross_Reference.pdf