MGU Computer Science Series — Programming with Python
Part IV: Data Structures
Indexes · Slicing · Methods · Search · Formatting
Chapter 16 of 30
16
MGU Computer Science Series  ·  Programming with Python  ·  Library Textbook Edition

Chapter 16:
Strings and Text
Processing

Reading text as a sequence of characters — indexing, slicing, searching, cleaning, splitting, joining, and formatting real text data

■ Part IV of VII ■

■ 10 Sections  •  Chapter 16 of 30 ■

Builds on Chapters 3, 4, and 9 — variables, input/output, and iteration

Chapter Introduction and Learning Objectives

Every useful program works with text. Programs read names, display messages, search for words, clean up data, format reports, parse input, and produce readable output. Text is everywhere: user responses, file contents, error messages, labels, identifiers, addresses, dates written as words, and records pulled from databases. Python's string type is the primary tool for handling all of it.

You have been working with strings since Chapter 3. You have printed them, stored them in variables, concatenated them, and formatted them with f-strings. In this chapter you go deeper. You will learn how strings are structured as sequences of characters, how to read specific characters or slices using indexes, how to apply Python's rich set of string methods, and how to write functions that analyze, clean, and transform text.

By the end of this chapter you should be able to read text character by character, extract substrings, search for patterns, count occurrences, change case, remove whitespace, split text into pieces, join pieces back together, and write a small but complete text analysis program. These skills connect directly to the work ahead: file processing in Chapter 21, CSV data in Chapter 22, and larger applications throughout Part VI.

Opening Question

A warehouse system needs to clean up product codes typed by different staff members — some with extra spaces, some in lowercase, some in uppercase. A teacher wants a program that counts how many times a student's essay uses the word "however." A data program needs to split a line like "Alice,28,Engineer,Boston" into separate pieces. What do these three tasks have in common? Each one treats text not as a label to display, but as data to examine, take apart, and rebuild. By the end of this chapter, you will have the tools to do exactly that.

10Sections
16Key Terms
8Programming Tasks
30Total Chapters

Learning Objectives

By the end of this chapter, you should be able to:

  1. 1Explain what it means for a string to be a sequence of characters, and identify the index of any character in a string.
  2. 2Use positive and negative indexes to read individual characters, and use len() to find a string's length.
  3. 3Extract substrings using slicing, including omitted bounds and step values.
  4. 4Explain string immutability and why string operations always return new strings.
  5. 5Apply common string methods for case conversion, whitespace removal, and content checking.
  6. 6Search text using the in operator, find(), index(), and count().
  7. 7Normalize and clean text data by combining case conversion and whitespace stripping.
  8. 8Split strings into lists of substrings and join lists of strings back into a single string.
  9. 9Format output using f-string format specifications for width, alignment, and decimal precision.
  10. 10Write a complete program that analyzes a block of text and reports meaningful statistics.

Sections at a Glance

Sections 16.1 – 16.3
The Structure of Strings: Characters, Indexes, and Slices
Sequences · Zero-Based Indexing · Substrings

Strings as Text Data

A string is a sequence of characters. It can hold letters, digits, punctuation, spaces, symbols, and special characters. You write string values using either single quotes or double quotes, and Python treats both the same way.

greeting = "Hello, world!" name = 'Fatima Al-Amin' empty = "" sentence = "The temperature is -12 degrees."

Strings can hold any printable text. They can be short or very long. An empty string ("") is a valid string with zero characters.

What strings are used for

You have probably used strings mainly for output — labeling results, printing messages, building prompts. But strings also carry data. Consider these examples:

  • A program reads a user's name and checks whether it starts with a capital letter.
  • A program opens a file and searches each line for a particular word.
  • A program cleans a list of product codes by stripping extra spaces and converting everything to uppercase.
  • A program splits a sentence into individual words and counts how many times each word appears.

All of these tasks require understanding strings not just as values to display, but as structured data to examine and transform.

Strings are made of characters

Every character in a string occupies exactly one position. A single letter, a space, a digit, a hyphen, an exclamation point — each one is a character. The string "cat" has three characters. The string "Hello!" has six: H, e, l, l, o, and !. The space character counts too: "hot dog" has seven characters.

Python gives you tools for working with individual characters, groups of characters, and the whole string at once.

Characters and Indexes

Because a string is a sequence, each character has a numbered index — its position in the string. Python indexes start at zero, not one.

H
0
e
1
l
2
l
3
o
4
,
5
 
6
w
7
o
8
r
9
l
10
d
11
!
12

To read one character, use square brackets with the index number.

word = "Hello, world!" print(word[0]) # H print(word[1]) # e print(word[7]) # w print(word[12]) # !

Negative indexes

Python also allows negative indexes, which count backward from the end of the string. The last character is always at index -1, the second-to-last at -2, and so on.

word = "Hello, world!" print(word[-1]) # ! print(word[-2]) # d print(word[-6]) # w

Negative indexes are useful when you want the end of a string but do not know its length ahead of time.

The len() function

The built-in len() function returns the number of characters in a string.

word = "Hello, world!" print(len(word)) # 13

Since indexes start at zero, the valid positive indexes for a string of length n are 0 through n - 1. Trying to access word[13] in the example above would produce an IndexError.

word = "Hello" print(word[5]) # IndexError: string index out of range
Watch Out

Always remember: len() gives you the count, but the last index is len(string) - 1. Off-by-one mistakes here are one of the most common beginner errors with strings.

Reading characters in a loop

You can combine len() with a for loop to visit every character in a string.

name = "Priya" for i in range(len(name)): print(i, name[i])
Output
0 P 1 r 2 i 3 y 4 a

This pattern is useful when you need the index number alongside the character. If you only need the character itself, you can iterate more directly, as you will see in Section 16.6.

Slicing Strings

You can extract a portion of a string using a slice. A slice uses square brackets with two index numbers separated by a colon: string[start:stop]. The result is a new string containing the characters from start up to but not including stop.

message = "Good morning, everyone!" print(message[0:4]) # Good print(message[5:12]) # morning print(message[14:22]) # everyone
Mental Model

Think of the colon as meaning "from here up to there, not including the stop position." The stop index is always excluded — this is the single most important rule to remember about slicing.

Omitting start or stop

If you omit the start index, Python begins from the very beginning of the string. If you omit the stop index, Python goes all the way to the end.

message = "Good morning, everyone!" print(message[:4]) # Good (start at 0) print(message[14:]) # everyone! (go to end) print(message[:]) # Good morning, everyone! (full copy)

Slicing with negative indexes

Negative indexes work in slices too.

filename = "report_2024.txt" print(filename[-3:]) # txt print(filename[:-4]) # report_2024

This is commonly used to extract or remove file extensions, check endings, or strip known suffixes.

Slices produce new strings

Slicing never modifies the original string. It produces a new string value. The original remains unchanged. You will learn why in Section 16.4.

greeting = "Hello, world!" part = greeting[7:12] print(part) # world print(greeting) # Hello, world! (unchanged)

Using a step in slices

Slices can also take a third value: string[start:stop:step]. This controls how many positions to skip between characters.

alphabet = "abcdefghijklmnopqrstuvwxyz" print(alphabet[0:10:2]) # acegi print(alphabet[::3]) # adgjmpsvy print(alphabet[::-1]) # zyxwvutsrqponmlkjihgfedcba
Use With Care

The step of -1 reverses the string. While useful to know, step-based slicing can be confusing and should be used carefully and sparingly. Favor clarity over compact tricks, especially in code other people will read.

Sections 16.4 – 16.6
Working With Strings: Immutability, Methods, and Search
New Strings, Not Changed Strings · Built-In Methods · Finding Text

String Immutability

Strings in Python are immutable. This means once a string is created, you cannot change any of its characters. You can read characters, extract slices, and create new strings based on old ones — but you cannot replace a character in place.

word = "cat" word[0] = "b" # TypeError: 'str' object does not support item assignment

This surprises many beginners. If strings cannot be changed, how do you "modify" one? The answer is: you build a new string.

word = "cat" new_word = "b" + word[1:] print(new_word) # bat print(word) # cat

You used slicing and concatenation to produce a new string value. The original "cat" was never touched. The variable new_word points to a brand new string.

Why immutability matters

Immutability makes strings safe to pass around in programs. If a function receives a string, it cannot accidentally modify the original. This prevents a common class of programming bugs. It also allows Python to store strings efficiently in memory.

As you work with string methods in the next sections, remember this rule: string methods never change the original string. They always return new string values. If you want to keep the result, you must assign it to a variable.

message = " hello " message.strip() # Does nothing useful — result is thrown away cleaned = message.strip() # Correct — save the result print(cleaned) # hello
The Single Most Important Rule in This Chapter

String methods never modify the original string. They always return a new string. If you call a method and do not store or use its return value, the result is simply lost. Every bug pattern in this chapter that involves "the string didn't change" traces back to forgetting this rule.

Common String Methods

Python strings come with a large collection of built-in methods — functions that belong to string objects and know how to work on the string they are called on. You call a method using dot notation: string.method().

This section covers the most important methods for everyday text processing.

Converting case

name = "fatima al-amin" print(name.upper()) # FATIMA AL-AMIN print(name.lower()) # fatima al-amin print(name.capitalize()) # Fatima al-amin print(name.title()) # Fatima Al-Amin

upper() converts all letters to uppercase. lower() converts all to lowercase. capitalize() makes the first letter uppercase and all others lowercase. title() capitalizes the first letter of each word.

These are especially useful for normalizing input. If you ask users to type a city name, one might type "paris" and another "PARIS". Converting both with .lower() lets you compare them reliably.

Stripping whitespace

raw = " hello, world! " print(raw.strip()) # hello, world! print(raw.lstrip()) # hello, world! (left stripped only) print(raw.rstrip()) # hello, world! (right stripped only)

strip() removes leading and trailing whitespace — spaces, tabs, and newline characters. lstrip() strips only from the left, rstrip() only from the right. Stripping is essential when reading data from files or user input, where extra spaces are common.

Checking start and end

filename = "report_2024.txt" print(filename.startswith("report")) # True print(filename.endswith(".txt")) # True print(filename.startswith("data")) # False

These return True or False and are useful in conditions.

if filename.endswith(".txt"): print("This is a text file.")

Replacing text

message = "I love cats. Cats are great." print(message.replace("cats", "dogs")) # I love dogs. Cats are great.

replace(old, new) returns a new string with every occurrence of old replaced by new. Notice that replace() is case-sensitive: "cats" was replaced but "Cats" was not.

print(message.replace("cats", "dogs").replace("Cats", "Dogs")) # I love dogs. Dogs are great.

You can chain method calls like this, though chaining many steps can reduce readability.

Checking content

code = "A1B2C3" digits = "12345" letters = "hello" print(code.isalpha()) # False (has digits) print(digits.isdigit()) # True print(letters.isalpha()) # True print(letters.isspace()) # False
MethodReturns True if…
isalpha()All characters are letters
isdigit()All characters are digits
isalnum()All characters are letters or digits
isspace()All characters are whitespace
islower()All letters are lowercase
isupper()All letters are uppercase

These are useful for validating input.

Searching and Counting Text

Python gives you several ways to find out whether a string contains a particular character or substring, where it appears, and how many times.

The in operator

The simplest check is the in operator, which you have used with lists. It works with strings too.

sentence = "The quick brown fox" print("fox" in sentence) # True print("cat" in sentence) # False print("quick" in sentence) # True print("Quick" in sentence) # False (case-sensitive)

in returns True if the substring appears anywhere inside the string.

Finding position with find() and index()

find() returns the index of the first occurrence of a substring. If it is not found, it returns -1.

message = "Good morning, everyone!" print(message.find("morning")) # 5 print(message.find("evening")) # -1

index() does the same thing, but raises a ValueError if the substring is not found. Use find() when absence is a normal possibility, and index() when you expect the substring to always be present.

print(message.index("everyone")) # 14 print(message.index("evening")) # ValueError: substring not found

Counting occurrences

count() counts how many times a substring appears.

sentence = "she sells sea shells by the sea shore" print(sentence.count("sea")) # 2 print(sentence.count("s")) # 8 print(sentence.count("she")) # 2

Iterating over characters

To examine every character in a string, use a for loop directly:

word = "Python" for character in word: print(character)
Output
P y t h o n

This is cleaner than looping with range(len(word)) when you do not need the index. Here is a practical example: counting how many vowels a word contains.

def count_vowels(text): vowels = "aeiouAEIOU" count = 0 for character in text: if character in vowels: count += 1 return count print(count_vowels("programming")) # 3 print(count_vowels("RHYTHM")) # 0
Sections 16.7 – 16.9
Cleaning and Shaping Text: Normalization, Splitting, Formatting
Real-World Messiness · split() and join() · Aligned Output

Case Conversion and Whitespace

Text data in the real world is messy. Users type inconsistently. Files have extra spaces. Systems export data in different formats. Two of the most common cleaning tasks involve case and whitespace.

Normalizing case for comparison

Consider a program that asks users to type a country name. Different users might type:

france France FRANCE fRaNcE

All of these mean the same thing, but string comparison is case-sensitive. "france" == "FRANCE" is False. The solution is to normalize before comparing.

response = input("Enter a country: ") if response.lower() == "france": print("Bonjour!")

By converting the response to lowercase before comparing, the program works correctly regardless of how the user typed it.

Handling whitespace in input

Users often type leading or trailing spaces accidentally, especially on mobile devices. Always strip user input before using it.

name = input("Enter your name: ").strip()

This one-liner takes the input, immediately strips whitespace, and assigns the cleaned result to name.

Worked Example
Cleaning a product code
  • ScenarioImagine a program that reads product codes entered by warehouse staff. The codes should be uppercase and have no extra spaces.
  • FunctionHere is a function that enforces this:
def clean_code(raw_code): return raw_code.strip().upper() entries = [" abc123", "DEF456 ", " ghi789 "] for entry in entries: print(clean_code(entry))
Output
ABC123 DEF456 GHI789

Each code was stripped and uppercased. The original strings were not changed; clean_code() returned new strings.

Splitting and Joining

One of the most powerful string tools is split(), which breaks a string into a list of smaller strings. Its partner, join(), reassembles pieces back into a single string.

Splitting on whitespace

When called with no arguments, split() splits on any whitespace — spaces, tabs, or newlines — and removes the whitespace from the results.

sentence = "The quick brown fox" words = sentence.split() print(words) # ['The', 'quick', 'brown', 'fox']

The result is a list of strings. You can loop over it, index into it, and measure its length.

print(len(words)) # 4 print(words[0]) # The print(words[-1]) # fox

Splitting on a delimiter

You can split on any character or substring by passing it as an argument.

date = "2024-03-15" parts = date.split("-") print(parts) # ['2024', '03', '15'] print(parts[0]) # 2024 print(parts[2]) # 15
csv_row = "Alice,28,Engineer,Boston" fields = csv_row.split(",") print(fields) # ['Alice', '28', 'Engineer', 'Boston']
Looking Ahead

Splitting on commas is exactly how CSV data is processed. You will use this technique extensively in Chapter 22.

Joining a list into a string

join() is the reverse of split(). It takes a list of strings and joins them into one string, placing a separator between each piece.

The syntax is slightly unusual: you call join() on the separator string.

words = ["The", "quick", "brown", "fox"] sentence = " ".join(words) print(sentence) # The quick brown fox
parts = ["2024", "03", "15"] date = "-".join(parts) print(date) # 2024-03-15
tags = ["python", "programming", "beginner"] print(", ".join(tags)) # python, programming, beginner print(" | ".join(tags)) # python | programming | beginner

A practical pattern: split, process, join

A common workflow is to split text into parts, modify the parts, then join them back together.

sentence = "the quick brown fox" words = sentence.split() capitalized = [] for word in words: capitalized.append(word.capitalize()) result = " ".join(capitalized) print(result) # The Quick Brown Fox

This is cleaner and more explicit than trying to modify the string directly.

Formatting Text

You have used f-strings for basic formatting since Chapter 4. This section introduces additional formatting tools that make output easier to read, especially when displaying tables or aligned data.

F-strings with format specifications

An f-string can include format specifications after a colon inside the curly braces. These control width, alignment, and decimal places.

Setting a minimum width:

name = "Alice" score = 95 print(f"{name:10} | {score}") # Alice | 95

The :10 tells Python to use at least 10 characters for the name, padding with spaces on the right.

Aligning text:

print(f"{'Name':10} | {'Score':>8}") print(f"{'Alice':10} | {95:>8}") print(f"{'Bob':10} | {87:>8}") print(f"{'Fatima':10} | {100:>8}")
Output
Name | Score Alice | 95 Bob | 87 Fatima | 100

< aligns left, > aligns right, ^ centers. These are most useful when building report output.

Controlling decimal places:

average = 87.333333 print(f"Average: {average:.2f}") # Average: 87.33 print(f"Average: {average:.0f}") # Average: 87

.2f means "floating-point number, two decimal places."

The center(), ljust(), and rjust() methods

For simple padding without f-strings:

title = "Report" print(title.center(30, "-")) # ------------Report------------ print(title.ljust(20, ".")) # Report.............. print(title.rjust(20)) # Report

These are useful for formatting section headings in text reports.

Worked Example
Building a formatted table
  • GoalDisplay a table of student scores with aligned columns.
students = [ ("Alice", 92), ("Bob", 87), ("Fatima", 100), ("Carlos", 74), ] header = f"{'Name':<10} | {'Score':>6}" divider = "-" * len(header) print(header) print(divider) for name, score in students: print(f"{name:<10} | {score:>6}")
Output
Name | Score --------------------- Alice | 92 Bob | 87 Fatima | 100 Carlos | 74

This is exactly the kind of console output you will produce when generating reports from files and CSV data in later chapters.

Section 16.10
Putting It Together: A Complete Text Analysis Program
Counting · Searching · Finding Extremes · Reporting

Basic Text Analysis

With all these tools, you can now write meaningful programs that analyze text — counting words, finding patterns, computing statistics, and summarizing content. This section ties the chapter together with a complete worked example.

Counting words

def count_words(text): words = text.split() return len(words) sample = "The quick brown fox jumps over the lazy dog" print(count_words(sample)) # 9

Counting a specific word (case-insensitive)

def count_word(text, target): words = text.lower().split() target = target.lower() count = 0 for word in words: if word == target: count += 1 return count poem = "Shall I compare thee to a summer's day? Thou art more lovely and more temperate." print(count_word(poem, "more")) # 2

Finding the longest word

def longest_word(text): words = text.split() if not words: return "" longest = words[0] for word in words: if len(word) > len(longest): longest = word return longest sentence = "Programming with Python is educational and rewarding" print(longest_word(sentence)) # educational

Counting character types

def analyze_characters(text): letters = 0 digits = 0 spaces = 0 other = 0 for char in text: if char.isalpha(): letters += 1 elif char.isdigit(): digits += 1 elif char.isspace(): spaces += 1 else: other += 1 return letters, digits, spaces, other text = "Hello, World! 123" l, d, s, o = analyze_characters(text) print(f"Letters: {l}") print(f"Digits: {d}") print(f"Spaces: {s}") print(f"Other: {o}")
Output
Letters: 10 Digits: 3 Spaces: 2 Other: 2
Worked Example
A complete text analysis function
  • GoalProduce a short report on any text string, combining nearly everything from this chapter.
def text_report(text): words = text.split() word_count = len(words) char_count = len(text) char_no_spaces = len(text.replace(" ", "")) if word_count > 0: avg_word_length = char_no_spaces / word_count else: avg_word_length = 0 longest = max(words, key=len) if words else "" unique_words = len(set(w.lower() for w in words)) print(f"Word count: {word_count}") print(f"Character count: {char_count}") print(f"Chars (no spaces): {char_no_spaces}") print(f"Unique words: {unique_words}") print(f"Avg word length: {avg_word_length:.2f}") print(f"Longest word: {longest}") sample = ( "Python is a great programming language. " "Python is easy to read and easy to write. " "Many programmers love Python." ) text_report(sample)
Output
Word count: 18 Character count: 103 Chars (no spaces): 87 Unique words: 14 Avg word length: 4.83 Longest word: programming
Why This Example Matters

This function brings together split(), len(), replace(), set(), max(), f-string formatting, and a conditional for the edge case of empty text. It is a real program, not a toy.

Chapter Summary

Key Terms

string
A sequence of characters used to represent text.
index
A number indicating the position of a character in a string. Indexes start at zero.
negative index
An index that counts from the end of the string. -1 refers to the last character.
slice
A portion of a string extracted using string[start:stop].
immutable
Cannot be changed after creation. Strings are immutable.
string method
A function that belongs to a string object and is called using dot notation.
len()
A built-in function that returns the number of characters in a string.
upper() / lower()
Methods that return all-uppercase or all-lowercase versions of a string.
strip()
A method that removes leading and trailing whitespace.
find()
A method that returns the index of the first occurrence of a substring, or -1 if not found.
count()
A method that returns the number of times a substring appears.
replace()
A method that returns a new string with all occurrences of one substring replaced by another.
split()
A method that breaks a string into a list of substrings.
join()
A method called on a separator string that assembles a list of strings into one.
format specification
A syntax inside f-string curly braces that controls width, alignment, and precision.
text analysis
The use of string operations to extract information or statistics from text data.
PracticeCode Reading

Read each code example carefully. Do not run it yet. Answer the questions.

Exercise 1

text = "abcdefgh" print(text[2]) print(text[-3]) print(text[1:5]) print(text[:3]) print(text[5:])
  1. What does text[2] produce?
  2. What does text[-3] produce?
  3. What does text[1:5] produce?
  4. What does text[:3] produce?
  5. What does text[5:] produce?

Exercise 2

message = " Hello, World! " result = message.strip().lower().replace("world", "Python") print(result)
  1. What does message.strip() return?
  2. What does the .lower() call produce after strip?
  3. What does .replace("world", "Python") do to that string?
  4. What is the final value of result?

Exercise 3

line = "Alice,32,Designer,New York" parts = line.split(",") print(parts[0]) print(parts[-1]) print(len(parts))
  1. What is parts after the split?
  2. What does parts[0] print?
  3. What does parts[-1] print?
  4. What does len(parts) return?

Exercise 4

def mystery(text): words = text.split() total = 0 for word in words: total += len(word) return total / len(words) print(mystery("The quick brown fox"))
  1. What does text.split() return?
  2. What does the loop accumulate in total?
  3. What value does the function return?
  4. What does this function compute?
PracticeCode Tracing

Trace through each program step by step, recording variable values as they change.

Tracing Exercise 1

s = "programming" result = "" for char in s: if char in "aeiou": result += char print(result)

Trace each iteration. What is result at the end?

Tracing Exercise 2

words = ["apple", "banana", "cherry", "date"] output = [] for word in words: if len(word) > 5: output.append(word.upper()) print(" | ".join(output))

Trace the loop. What is in output at the end? What does join() produce?

Tracing Exercise 3

text = "2024-11-05" parts = text.split("-") year = int(parts[0]) month = int(parts[1]) day = int(parts[2]) print(f"Year: {year}, Month: {month}, Day: {day}")

Trace each step. What is the final output?

PracticePredict the Output

Write your prediction before checking with Python.

  1. s = "Hello" print(s[0] + s[-1])
  2. print("Python"[2:5])
  3. words = "one two three".split() print(words[1])
  4. print("-".join(["a", "b", "c", "d"]))
  5. name = " Fatima " print(name.strip().upper())
  6. text = "banana" print(text.count("a"))
  7. s = "abcde" print(s[::-1])
  8. sentence = "the sky is blue" words = sentence.split() print(words[-2])
PracticeDebugging and Error Analysis

Each example contains an error. Identify the error, explain what goes wrong, and write the corrected code.

Bug 1

greeting = "Hello" greeting[0] = "J" print(greeting)

What type of error is this? Why? How would you correctly create "Jello" from "Hello"?

Bug 2

text = "programming" last = text[len(text)] print(last)

What error does this produce? Why? What is the correct index for the last character?

Bug 3

message = "good morning" result = message.Strip() print(result)

What is wrong here?

Bug 4

words = ["hello", "world"] sentence = words.join(" ") print(sentence)

What is wrong with the .join() call? Write the correct version.

Bug 5

def remove_spaces(text): text.replace(" ", "") return text result = remove_spaces("hello world") print(result)

The function does not produce the expected result. Explain why. Fix it.

Bug 6

csv_line = "Alice,28,Engineer" name = csv_line.split[0] print(name)

What is the syntax error?

PracticeShort Programming Tasks

Complete each task. Test your program with at least two different inputs.

  1. Task 1. Write a function reverse_string(text) that returns the string in reverse order. Use slicing. Do not use any method named reverse.
  2. Task 2. Write a function is_palindrome(text) that returns True if the text reads the same forward and backward (ignoring case), and False otherwise. Test with "racecar", "hello", "Madam", and "level".
  3. Task 3. Write a function count_words(text) that returns the number of words in a string. Use split().
  4. Task 4. Write a function clean_name(name) that strips whitespace and applies .title() to format a name properly. Test with " alice johnson " and "BOB SMITH".
  5. Task 5. Write a function initials(full_name) that takes a full name like "Fatima Al-Amin" and returns the initials like "F.A.". Split on whitespace and take the first character of each word.
  6. Task 6. Write a function word_lengths(sentence) that takes a sentence and prints each word alongside its length. Format the output so the word column is 15 characters wide.
    Example output for "The quick fox"
    The 3 quick 5 fox 3
  7. Task 7. Write a function replace_vowels(text, replacement) that returns the text with every vowel replaced by the given character. Example: replace_vowels("hello world", "*") → "h*ll* w*rld"
  8. Task 8. Write a function is_valid_email(text) that returns True if the text contains exactly one @ character and at least one . after the @. This is a simplified check, not a complete validator.
Mini-Project: Text Analyzer
Combine the full chapter into one working program

Write a program that reads a paragraph of text from the user (or uses a built-in sample) and displays a complete analysis report. The program should be organized with functions.

Required Features — Your Program Must Compute and Display
  • Total word count
  • Total character count (including spaces)
  • Total character count (excluding spaces)
  • Number of unique words (case-insensitive)
  • Average word length (to two decimal places)
  • The longest word
  • The shortest word
  • The most common starting letter (among all words)
  • Number of words longer than five letters
  • The words in alphabetical order (display the first five only)
Program Structure — Use at Least Four Functions
  • get_words(text) — returns a clean list of lowercase words
  • basic_stats(words) — returns counts and lengths
  • word_analysis(words) — finds longest, shortest, most common letter
  • display_report(text, words) — prints the formatted report
Sample Output
---- TEXT ANALYSIS REPORT ---- Input text: Python is a great programming language. Python is easy to read. Word count: 12 Char count: 63 Chars (no spaces): 54 Unique words: 10 Avg word length: 4.50 Longest word: programming Shortest word: a Most common letter: p Words over 5 chars: 2 First 5 alphabetically: a, easy, great, is, language
Extension Ideas

If you finish early, add any of these: count sentences (split on ., !, and ?); count how many words appear more than once; display words in order from longest to shortest; check whether the text is a pangram (contains every letter of the alphabet).

Mixed ReviewCumulative Review

These questions draw on earlier chapters.

  1. Write a function print_table(rows) that takes a list of tuples (name, score) and prints a formatted table. Use what you know about f-strings, loops, and lists.
  2. Write a loop that reads words from the user until the user types "quit", then prints all the words they entered in alphabetical order. Use a while loop and a list.
  3. A function receives a list of strings. It should return a new list containing only the strings that start with a capital letter. Write that function. Use what you know about iteration, conditionals, and the isupper() or indexing approach.
  4. Write a function make_initials(names) that accepts a list of full names and returns a list of initials strings. Use your initials() function from the short tasks section, or write a new version here.
  5. Write a program that asks the user to enter three sentences, stores them in a list, and then prints a word-count summary for each sentence.
ReviewReview Questions
  1. What does it mean to say that strings are sequences?
  2. Why do Python string indexes start at zero rather than one?
  3. What is the difference between find() and index()?
  4. Why do string methods return new values instead of changing the original string?
  5. What does split() return when called with no arguments on a string that contains multiple spaces between words?
  6. What does join() do, and why is its syntax written as separator.join(list) instead of list.join(separator)?
  7. When would you use strip() on user input? Give a realistic example.
  8. What format specification would you use to display a float with exactly three decimal places in an f-string?
  9. What is the difference between isalpha() and isalnum()?
  10. Explain the relationship between split() and join(). When would you use both together?

Common Misconceptions

Misconception 1
"Calling a string method changes the string."
Strings are immutable. A method like .strip() or .upper() never modifies the original string — it returns a brand new string. If you call message.strip() without assigning the result to a variable, the cleaned-up string is created and then immediately discarded. message itself remains exactly as it was.
Misconception 2
"A slice like text[2:5] includes the character at index 5."
The stop index in a slice is always excluded. text[2:5] includes indexes 2, 3, and 4 — three characters, not four. This "up to but not including" rule applies consistently across slicing, range(), and many other Python constructs.
Misconception 3
"join() is a list method, so you call it on the list."
It is easy to expect words.join(" "), but Python's syntax is reversed: join() belongs to the separator string, and the list is passed in as the argument: " ".join(words). This design allows any string to act as a separator without needing a special method on every list.
Misconception 4
"String comparison ignores case, the way humans read text."
Python string comparisons are case-sensitive by default. "Paris" == "paris" is False. If you want case-insensitive comparison, you must explicitly normalize both sides, typically with .lower(), before comparing.
Misconception 5
"split() always splits on a single space character."
When called with no arguments, split() splits on any run of whitespace — including multiple spaces, tabs, and newlines — and automatically discards empty results. This is different from split(" "), which splits on each individual space character and can produce empty strings if there are multiple consecutive spaces.
Chapter 16 Checklist
Use this to confirm your understanding before moving to Chapter 17
Concepts
  • I can explain what it means for a string to be a sequence of characters.
  • I can use positive and negative indexes to read characters from a string.
  • I can use len() to find the length of a string and avoid off-by-one IndexErrors.
  • I can extract substrings using slicing, including omitted bounds and steps.
  • I can explain why strings are immutable and why methods return new strings.
  • I can apply case-conversion, whitespace, and content-checking string methods.
  • I can search text using in, find(), index(), and count().
  • I can split a string into a list and join a list back into a string.
  • I can format output using f-string width, alignment, and precision specifiers.
Applications
  • I can normalize and clean messy text input from users or data sources.
  • I can write a function that counts, searches, or analyzes text.
  • I can build a simple aligned report or table using formatted output.
  • I can complete a multi-function program that produces a full text analysis report.
Debugging Habits
  • I can recognize a TypeError caused by trying to assign to a string index.
  • I can recognize an IndexError caused by an off-by-one mistake with len().
  • I know to check whether a method's result was assigned before assuming it "didn't work."
Preview — Chapter 17
Lists and Sequences

Chapter 16 treated strings as sequences of characters. Chapter 17 introduces lists as sequences of values — and lists can hold anything: numbers, strings, other lists, even mixed types. Like strings, lists support indexing, slicing, and iteration. But unlike strings, lists are mutable: you can change, add, and remove items after creating the list.

Lists are one of the central tools of practical Python programming. In Chapter 17 you will learn to create lists, access and update elements, use the most important list methods, loop through lists efficiently, and apply common list patterns for totals, counts, and finding extreme values. The skills you practiced with strings in this chapter will transfer directly — but lists open the door to storing and processing collections of real data.

MGU Library Connection
  • Programming with Python — Appendix C: Python Syntax Quick Reference
  • Programming with Python — Appendix D: Common Python Error Messages
  • Programming with Python — Appendix H: Data Structure Reference
  • Companion resource: String Methods Chart
  • Companion resource: Debugging Checklist
  • Next chapter: Lists and Sequences (Chapter 17, Part IV)