Grade by Grade
A. Programming Concepts and Skills · Arrays and Subprograms

Strings 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.
wordnegative indexS0-8n1-7o2-6w3-5s4-4h5-3o6-2e7-1word[4:8] → "shoe"word[:4] → "Snow"index
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.

"Sudbury,Kenora,Timmins".split(",")"Sudbury""Kenora""Timmins"" / ".join(towns)"Sudbury / Kenora / Timmins"a stringtowns (a list)a string
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 example

Example 1: Counting vowels with a loop

Count the vowels in the sentence "Eh, it is cold in Iqaluit today".

  1. Convert to lower case first, so that "E" and "I" count too.
  2. Loop over the characters. ch in "aeiou" checks whether the character is one of the five vowels.
  3. 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")
  4. Check by hand: e, i, i, o, i, I, a, u, i, o, a = 11.

11 vowels

Worked example

Example 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.)

  1. Make the check case-insensitive with upper().
  2. Length must be 7 and index 3 must be a space.
  3. Letters are at indexes 0, 2, 5; digits at 1, 4, 6. Build two small strings and test them with isalpha() and isdigit().
  4. 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 fun

A 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 activity

Username 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.

  1. 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.
  2. Work out each person's username by hand first.
  3. 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.

  1. 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

1

For s = "Manitoba", what are s[0], s[-1] and len(s)?

2

For s = "Manitoba", what is s[2:6]?

3

Write a slice that gives the last 3 characters of any string s.

4

Why does s[0] = "m" crash? Write a line that makes the first letter lower case instead.

5

What does "snow day".find("day") give? What about .find("ice")?

6

What does "10 20 30".split() give?

7

Predict: print("-".join(["2026", "12", "25"]))

More practice: warm-up

1

What is len("O Canada")?

2

What is "maple".upper()?

3

What is "hockey"[1:3]?

4

Does "Inuvik"[10:20] crash?

5

What does "abc"[::-1] give?

More practice: on your own

1

Predict the output:

p = "613-555-0199"

print(p[:3], p[4:7], p[-4:])

2

Write code that counts the spaces in a string s without using count().

3

Write a line that checks whether a word w is a palindrome (reads the same backwards, like "kayak").

4

Predict the output:

t = "banana"

print(t.count("a"), t.find("n"), t.replace("a", "o", 1))

5

A user types " Sault Ste. Marie ". Write code that removes the outer spaces and prints the number of words.

Challenge

1

Write 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".

2

Explain, with an example, why s.find(x) and a list linear search returning −1 follow the same design.

3

Write code that turns a name like "marie-claire gagnon" into "Marie-Claire Gagnon" using only slicing, upper(), lower() and a loop (not title()).

Check your understanding

Quiz

1.

What is "Toronto"[2:5]?

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 teachers

Big 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).