A. Programming Concepts and Skills · Arrays and SubprogramsStrings as Sequences: Characters, Substrings and String Methods
A3.1 A1.5 A2.3About 70 minutes
Learning goals- I can treat a string as a sequence of characters and use indexes, len() and loops on it.
- I can take a substring with slicing, a[start:stop], and predict exactly which characters it includes.
- I can use existing string subprograms (methods) such as upper, find, replace, split and join.
- I can explain why strings are immutable and build a new string instead of changing one.
Words to know
character
One letter, digit, space or symbol. A string is a sequence of characters.
substring
A part of a string made of consecutive characters, e.g. "shoe" in "Snowshoe".
slice
Python's way to take a substring or sub-list: s[start:stop] includes index start and stops just before index stop.
immutable
Cannot be changed after it is created. Strings are immutable; lists are not.
method
A subprogram that belongs to a value and is called with a dot, e.g. name.upper().
split / join
split breaks a string into a list of strings; join glues a list of strings into one string.
Every string is an array of characters
When you type your school username or a postal code into a website, a program checks it character by character: Is it the right length? Is the fourth character a space? Are these characters letters and those digits? To do that, the program treats the string the same way you treated a list: as a sequence with indexes.
The curriculum names substring as one of the existing subprograms you should be able to use. In Python, substrings come from slicing, and dozens of ready-made string methods do the rest.
Indexing a string
Everything you learned about list indexes works on strings:
s[0] is the first character and s[-1] the last.len(s) is the number of characters (spaces and punctuation count).- The bounds are 0 to
len(s) - 1, and going past them raises IndexError: string index out of range. for ch in s: visits each character in turn.
Slicing: taking a substring
s[start:stop] gives the characters from index start up to, but not including, index stop. The number of characters is stop - start.
- Leave out start to begin at 0:
s[:4] is the first 4 characters. - Leave out stop to go to the end:
s[4:]. - A third number is the step:
s[1:6:2] takes every second character. s[::-1] reverses the string. - Slices never raise IndexError. A slice that runs past the end simply stops at the end.
The string "Snowshoe" has 8 characters, indexes 0 to 7 (negative indexes −8 to −1 on top). The slice word[4:8] takes indexes 4, 5, 6 and 7 (shaded): stop = 8 is not included. word[:4] takes indexes 0 to 3.word = "Snowshoe"
print(word[0], word[-1], len(word))
print(word[4:8])
print(word[:4])
print(word[4:])
print(word[1:6:2])
print(word[::-1])
Output, line by line: S e 8, shoe, Snow, shoe, nwh (indexes 1, 3, 5), eohswonS.
Slices work on lists too
a[1:4] is a new list holding elements 1, 2 and 3 of a, and a[:] copies a whole list (you saw that in Declaring, Initializing, Accessing and Modifying Lists). Many languages call this taking a sub-array.
Strings are immutable
You can read s[0], but you cannot change it. s[0] = "J" raises TypeError: 'str' object does not support item assignment. Instead, build a new string and assign it to the variable:
name = "jade" then name = name[0].upper() + name[1:] makes "Jade".
Every string method below also returns a new string; the original is unchanged unless you reassign it.
Useful string methods
s.upper(), s.lower(): all capitals / all lower case.s.strip(): removes spaces (and newlines) from both ends.s.find(sub): the index where sub first appears, or −1 if it is not there. (A linear search, just like yours!)s.replace(old, new): a copy with every old replaced by new.s.count(sub): how many times sub appears.s.startswith(sub), s.endswith(sub): True or False.s.isdigit(), s.isalpha(): True if every character is a digit / a letter (and s is not empty).s.split(sep): a list of the pieces between separators (spaces if no sep is given).sep.join(list_of_strings): one string with sep between the pieces.
city = " Thunder Bay "
c = city.strip()
print(c.upper())
print(c.lower())
print(c.find("Bay"), c.find("Ottawa"))
print(c.replace("Bay", "Cape"))
print(c.count("a"), c.startswith("Th"))
Output: THUNDER BAY, thunder bay, 8 -1, Thunder Cape, 1 True. Only one lower-case "a" (in "Bay"); count is case-sensitive.
split(",") turns one string into a list of strings at every comma. join goes the other way, gluing the list's strings together with the separator " / " between them.Worked exampleExample 1: Counting vowels with a loop
Count the vowels in the sentence "Eh, it is cold in Iqaluit today".
- Convert to lower case first, so that "E" and "I" count too.
- Loop over the characters.
ch in "aeiou" checks whether the character is one of the five vowels. - Code:
sentence = "Eh, it is cold in Iqaluit today"
vowels = 0
for ch in sentence.lower():
if ch in "aeiou":
vowels += 1
print(vowels, "vowels") - Check by hand: e, i, i, o, i, I, a, u, i, o, a = 11.
Worked exampleExample 2: Checking a Canadian postal code
Canadian postal codes have the pattern letter digit letter space digit letter digit, e.g. K2P 1L4. Write a function that returns True for a correctly formatted code (upper or lower case). (You will learn to write functions with def and return in detail in Writing Your Own Functions; you met simple ones in Grade 9.)
- Make the check case-insensitive with
upper(). - Length must be 7 and index 3 must be a space.
- Letters are at indexes 0, 2, 5; digits at 1, 4, 6. Build two small strings and test them with
isalpha() and isdigit(). - Code:
def check(code):
code = code.upper()
if len(code) != 7 or code[3] != " ":
return False
letters = code[0] + code[2] + code[5]
digits = code[1] + code[4] + code[6]
return letters.isalpha() and digits.isdigit()
print(check("k2p 1l4"))
print(check("K2P1L4"))
print(check("K2P 1LL"))
Output: True (lower case is accepted), False (no space, length 6), False (the last character is a letter).
This checks the format only. Real postal codes have extra rules (for example, some letters are never used), so a full check would need more tests.
Just for funA string walks into a bar and orders a drink. The bartender says, "Sorry, we don't serve strings here."
The string goes outside, ties itself in a knot, messes up its ends and walks back in. "Aren't you a string?" asks the bartender.
"No," it says, "I'm a frayed knot."
Hands-on activityUsername generator
You need: a computer with Python, and the names and birth years of 3 family members or friends (with permission), or made-up ones.
- Many schools make usernames from parts of a name. Design a rule, for example: first letter of the first name + the first 6 letters of the last name + the last two digits of the birth year, all in lower case, with hyphens and spaces removed.
- Work out each person's username by hand first.
- Write Python that builds the username with slicing and methods. Here is a start:
first = "Maya"
last = "Okafor-Smith"
year = "2009"
user = first[0].lower() + last.lower().replace("-", "")[:6] + year[-2:]
print(user)
This prints mokafor09.
- Test a short last name (e.g. "Li") and a name with a space (e.g. "Van Dyke"). Does
[:6] crash when the name is shorter than 6? (It doesn't. Slices stop at the end.) Fix your rule for spaces with another replace.
What to look for: the student predicts the output before running and uses slices instead of single indexes where possible.
Your turn
1For s = "Manitoba", what are s[0], s[-1] and len(s)?
2For s = "Manitoba", what is s[2:6]?
"nito" (indexes 2, 3, 4, 5).
3Write a slice that gives the last 3 characters of any string s.
4Why does s[0] = "m" crash? Write a line that makes the first letter lower case instead.
Strings are immutable (TypeError). Use s = s[0].lower() + s[1:].
5What does "snow day".find("day") give? What about .find("ice")?
6What does "10 20 30".split() give?
['10', '20', '30'] (a list of strings).
7Predict: print("-".join(["2026", "12", "25"]))
More practice: warm-up
4Does "Inuvik"[10:20] crash?
No. Slices never raise IndexError; it gives the empty string "".
5What does "abc"[::-1] give?
More practice: on your own
1Predict the output:
p = "613-555-0199"
print(p[:3], p[4:7], p[-4:])
613 555 0199. The area code is indexes 0 to 2; the next three digits are indexes 4 to 6; the last four are the final four characters.
2Write code that counts the spaces in a string s without using count().
spaces = 0
for ch in s:
if ch == " ":
spaces += 1
3Write a line that checks whether a word w is a palindrome (reads the same backwards, like "kayak").
print(w.lower() == w.lower()[::-1]). For "Kayak" it prints True.
4Predict the output:
t = "banana"
print(t.count("a"), t.find("n"), t.replace("a", "o", 1))
3 2 bonana. The third argument 1 means replace only the first occurrence.
5A user types " Sault Ste. Marie ". Write code that removes the outer spaces and prints the number of words.
city = input().strip()
print(len(city.split()))
For that input it prints 3.
Challenge
1Write a program that reads a sentence and prints it with each word reversed but in the original order, e.g. "snow is fun" → "wons si nuf".
words = input().split()
for i in range(len(words)):
words[i] = words[i][::-1]
print(" ".join(words))
split makes a list, the index loop replaces each element, and join rebuilds the sentence.
2Explain, with an example, why s.find(x) and a list linear search returning −1 follow the same design.
Both scan from index 0, stop at the first match, and return its index, or the sentinel −1 when there is no match. E.g. "hello".find("l") is 2, the first match, and "hello".find("z") is −1.
3Write code that turns a name like "marie-claire gagnon" into "Marie-Claire Gagnon" using only slicing, upper(), lower() and a loop (not title()).
name = "marie-claire gagnon"
result = ""
new_word = True
for ch in name:
if new_word:
result += ch.upper()
else:
result += ch.lower()
new_word = ch == " " or ch == "-"
print(result)
A flag remembers whether the previous character was a space or hyphen; the result string is built up because strings are immutable.
Check your understandingQuiz
2. What happens with s = "cat" then s[0] = "b"?
3. What does "North Bay".find("z") return?
4. What does "a,b,,c".split(",") return?
5. Which slice gives the first 5 characters of s?
6. w = "ice" and then w.upper() is run on its own line. What is w afterwards?
For parents and teachersBig idea: A3.1 lists substring as an existing subprogram students should use. Strings are also a natural second kind of array (A1.5): indexes, bounds and loops work the same way. The vowel count reuses the counting algorithm (A2.3).
How to help: Write a word on paper with index numbers under each letter. Ask your teen for slices like [1:4] and [-3:], and check the answer by covering letters with your fingers. The username activity is a good real-world example.
Common difficulties:
- Including the stop index in a slice. Remember: stop is not included, and the length of
s[a:b] is b − a. - Expecting methods to change the string. They return a new one; reassign it.
- Forgetting that comparisons are case-sensitive (
"A" != "a"). Convert with lower() before comparing. - Confusing
split (string → list) with join (list → string), and calling join on the list instead of the separator: it is " ".join(words).