CSC108H1/Unit 4
Unit 4 · Intro to Programming

Strings

Text is the first data structure you will index and slice, and the first one that is immutable. That word does most of the work in this unit: string methods never change the string they are called on, they return a new one, and forgetting that produces code that looks right and does nothing.

By the end you can
  • Index and slice with confidence, including negative indices.
  • Explain why s.upper() alone accomplishes nothing.
  • Build strings in a loop and know why join is better.
  • Pick the right method from the common set.
Why it matters & where it comes from
Why it exists

Almost all data arrives as text: files, web responses, user input. Getting from text to structured values is the first half of nearly every real program, and slicing is the basic tool.

In the real world

Parsing dates, cleaning names, splitting CSV lines. The Heartbleed bug of 2014 was a length field trusted without checking, letting a read run past the end of a buffer — an indexing error with global consequences.

Story & open questions

Python 3 strings are sequences of Unicode code points, not bytes, which is why len('café') is 4 rather than 5. That change from Python 2 broke a great deal of code and is the main reason the migration took a decade.

Indexing and slicing

Definition

s[i] is the character at position i, counting from 0. Negative indices count from the end, so s[-1] is the last character. s[a:b] is the slice from a up to but not including b.

Example Every form
python
s = 'computer'
#    01234567
#   -87654321

s[0]      # 'c'
s[-1]     # 'r'
s[1:4]    # 'omp'    - positions 1, 2, 3
s[:3]     # 'com'    - start defaults to 0
s[5:]     # 'ter'    - end defaults to len(s)
s[:]      # 'computer' - a full copy
s[::2]    # 'cmue'   - every second character
s[::-1]   # 'retupmoc' - reversed

The excluded endpoint is the same convention as range, and it has the same payoff: s[:i] + s[i:] reconstructs s for any i, with no off-by-one adjustment.

Indexing raises, slicing does not

s[100] raises IndexError. But s[100:200] quietly returns ''. Slicing clamps to the valid range, which is convenient and occasionally hides a bug — an empty result may mean your indices were nonsense rather than that the data was empty.

Immutability

Rule Strings never change

No operation modifies a string in place. s[0] = 'x' is a TypeError. Every method that appears to alter a string in fact returns a new one, and the original is untouched.

Example The mistake everyone makes once
python
name = 'ada'
name.upper()        # computes 'ADA' and throws it away
print(name)         # 'ada' - unchanged

name = name.upper() # the return value has to be captured
print(name)         # 'ADA'

If a line consists only of something.method() on a string and nothing is assigned, that line does nothing at all.

Example Building a string in a loop
python
# works, but creates a new string every round
out = ''
for w in words:
    out = out + w + ' '

# idiomatic and faster
out = ' '.join(words)
Because strings are immutable, out + w copies everything accumulated so far. Over $n$ words that is $n^2$ character copies — a Unit 9 concern that shows up here first.
join is called on the separator, which reads backwards at first but means any separator works: ', '.join(names).

The methods worth memorising

Example The common set
python
s = '  Hello World  '

s.strip()            # 'Hello World'  - removes leading/trailing whitespace
s.lower()            # '  hello world  '
s.replace('l', 'L')  # replaces every occurrence
s.split()            # ['Hello', 'World'] - splits on any whitespace run
'a,b,c'.split(',')   # ['a', 'b', 'c']   - or on a given separator
s.find('World')      # 8  - index, or -1 if absent
s.count('l')         # 3
'World' in s         # True - the readable membership test
'abc'.startswith('a')# True
'42'.isdigit()       # True - useful before int()
Rule find versus index versus in
Use in when you only need to know whether something is present. It is the clearest.
Use find when you need the position and can handle -1 for absent.
Use index when absence is a genuine error; it raises ValueError rather than returning a sentinel that can be used by accident as a valid index.
-1 is a valid index

This is the trap in find. If you write s[s.find('z')] and 'z' is absent, the -1 silently gives you the last character rather than an error. Always test the result of find before using it.

Common pitfalls

• Calling a method and discarding the result. Strings are immutable; capture the return value.
• Assuming split() and split(' ') behave the same. The first collapses runs of whitespace, the second does not, so 'a b'.split(' ') gives a stray empty string.
• Forgetting the excluded endpoint on slices.
• Using find without checking for -1.

Practice

Predict each answer before running it. Original exercises written for this guide in the style of the course's weekly sets.

Practice 1 · Slice it

For s = 'abcdef', what are s[2:5], s[-2:], s[::-1]?

Answer
'cde', 'ef', 'fedcba'.
Practice 2 · Why unchanged?

Why does this print hello?

python
s = 'hello'
s.replace('h', 'j')
print(s)
Answer
replace returns a new string; nothing captured it. Write s = s.replace('h', 'j').
Practice 3 · Palindrome

Write a function testing whether a string reads the same backwards.

Solution
python
def is_palindrome(s: str) -> bool:
    """Return whether s reads the same forwards and backwards,
    ignoring case and spaces.

    >>> is_palindrome('Race car')
    True
    >>> is_palindrome('hello')
    False
    """
    clean = s.lower().replace(' ', '')
    return clean == clean[::-1]
Chaining lower() then replace() works because each returns a new string for the next to act on.
Practice 4 · split difference

What do 'a b'.split() and 'a b'.split(' ') give?

Answer
['a', 'b'] and ['a', '', 'b']. With no argument, runs of whitespace count as one separator; with an explicit ' ', each space separates, so the double space yields an empty field.
Practice 5 · Initials

Turn 'ada lovelace' into 'A.L.'.

Solution
python
def initials(name: str) -> str:
    """Return the dotted uppercase initials of name.

    >>> initials('ada lovelace')
    'A.L.'
    """
    return ''.join(w[0].upper() + '.' for w in name.split())
Practice 6 · find trap

What does 'hello'[('hello'.find('z'))] give, and why is that bad?

Answer
'o'. find returned -1, which is a legal index meaning "last". You get a plausible-looking wrong answer instead of an error.
Practice 7 · Count words

Count the words in a sentence.

Solution
len(sentence.split()). Splitting on whitespace and taking the length handles multiple spaces and leading or trailing space correctly, which counting spaces would not.
Practice 8 · Vowel removal

Remove all vowels from a string.

Solution
python
def strip_vowels(s: str) -> str:
    """Return s with all vowels removed.

    >>> strip_vowels('education')
    'dctn'
    """
    return ''.join(ch for ch in s if ch.lower() not in 'aeiou')
Build a new string rather than trying to delete from the old one — there is no way to delete from a string.

Expanded topic checklist

Course coverage. This unit is part of the complete CSC108H1 topic map. It should cover the following items before you move on:

  • Indexing and slicing. Positive and negative indices, defaults, step, and the excluded endpoint.
  • Immutability. Why methods return rather than modify.
  • Common methods. strip, split, join, replace, find, count, membership.
  • Building strings. Why join beats repeated concatenation.
Textbook/source alignment

The calendar lists strings among the elementary data types. Check the syllabus on Quercus for the assigned exercises and for which methods the course expects you to know without lookup.

Core idea map

Strings are immutable sequences. Unit 5 introduces lists, which are mutable sequences with almost the same indexing — and the difference between the two is where the aliasing trap lives.

Study routine. Whenever you call a string method, ask where the result is going. If the answer is nowhere, the line is dead code.

Deep practice extensions

Use these after the main practice set. They push on definitions and edge cases rather than routine calculation.

Extension 1 · Immutability

What happens on s[0] = 'x'?

Guide
TypeError: strings do not support item assignment. Build a new string instead.
Extension 2 · Slice bounds

What does s[10:20] give on a short string?

Guide
''. Slicing clamps rather than raising, unlike indexing.
Extension 3 · join direction

Why is join a method of the separator?

Guide
So any string can be the separator and the argument can be any iterable of strings. It reads oddly but generalises cleanly.
Extension 4 · Concatenation cost

Why is repeated += on a string slow?

Guide
Each one builds a whole new string, copying everything so far. Over $n$ appends that is quadratic work.