Lesson 8 - Lists & String Manipulation
Learning Objective
Create, index, and slice lists; use core list methods; and apply basic string methods (split, join, case conversion) to build formatted text from data.
Introduction
Lists let you group related values together instead of juggling separate variables for each one.
Strings, meanwhile, provide a rich set of built-in methods for splitting apart and reassembling text.
This lesson combines both ideas:
- Store structured data in lists
- Access and modify list contents
- Split text into smaller pieces
- Join pieces of text back together
- Format data into human-readable output
These patterns appear everywhere in real-world programming, from processing CSV files to generating status reports and API responses.
Creating Lists
A list is an ordered collection of values.
students = ["Aditi", "Rahul", "Meera"]
scores = [82, 91, 76]Lists can store:
- Strings
- Numbers
- Booleans
- Other lists
- Mixed data types
Example:
data = ["Python", 3.12, True]Indexing Lists
Lists use zero-based indexing.
students = ["Aditi", "Rahul", "Meera"]
print(students[0])Output:
AditiAccess the last item with negative indexing:
print(students[-1])Output:
MeeraSlicing Lists
Slicing extracts part of a list.
print(students[0:2])Output:
['Aditi', 'Rahul']The slice:
[start:end]includes start but excludes end.
Common List Methods
Adds an item to the end of a list.
students.append("Kabir")Returns the number of items.
len(students)Access a specific item.
students[0]
students[-1]Extract part of a list.
students[1:3]Code
# lists_and_strings.py
students = ["Aditi", "Rahul", "Meera"]
scores = [82, 91, 76]
print(students[0], scores[0]) # indexing
print(students[-1]) # negative indexing
print(students[0:2]) # slicing
students.append("Kabir")
scores.append(88)
print(students, scores)
print("Number of students:", len(students))
# pairing list data with zip + a for loop
for name, score in zip(students, scores):
print(f"{name}: {score}")
# basic string manipulation
report_line = "Aditi,82,Pass"
parts = report_line.split(",")
print(parts)
name, score_text, status = parts
summary = f"{name.upper()} scored {score_text} — {status}"
print(summary)
names_joined = ", ".join(students)
print("Class roster:", names_joined)Expected Output
Click to see expected output
Aditi 82
Meera
['Aditi', 'Rahul']
['Aditi', 'Rahul', 'Meera', 'Kabir'] [82, 91, 76, 88]
Number of students: 4
Aditi: 82
Rahul: 91
Meera: 76
Kabir: 88
['Aditi', '82', 'Pass']
ADITI scored 82 — Pass
Class roster: Aditi, Rahul, Meera, KabirWorking with Multiple Lists Using zip()
The zip() function pairs items from multiple lists.
students = ["Aditi", "Rahul"]
scores = [82, 91]
for name, score in zip(students, scores):
print(name, score)Output:
Aditi 82
Rahul 91This is a common way to process related data stored in separate lists.
Splitting Strings
The split() method breaks a string into a list.
report_line = "Aditi,82,Pass"
parts = report_line.split(",")Output:
['Aditi', '82', 'Pass']The comma acts as the separator.
Joining Strings
The join() method combines multiple strings into one string.
students = ["Aditi", "Rahul", "Meera"]
roster = ", ".join(students)Output:
Aditi, Rahul, MeeraThe separator appears between each item.
String Case Conversion
Convert text to uppercase:
name.upper()Convert text to lowercase:
name.lower()Example:
print("Python".upper())
print("Python".lower())Output:
PYTHON
pythonString Slicing
Strings support indexing and slicing just like lists.
word = "Python"
print(word[0])
print(word[-1])
print(word[0:3])Output:
P
n
PytThis works because strings are ordered sequences of characters.
🔧 Under the Hood
Why does .append() change the list, but .upper() doesn’t change the string?
Lists are mutable.
This means the existing list object can be modified directly.
Example:
students = ["Aditi", "Rahul"]
students.append("Kabir")After the call:
['Aditi', 'Rahul', 'Kabir']The original list itself changed.
Strings are immutable.
When you write:
name = "Aditi"
name.upper()Python creates a brand-new string:
"ADITI"and returns it.
The original string remains unchanged:
name
# "Aditi"That’s why code such as:
summary = name.upper()must use the returned value.
split() and join()
These methods are natural opposites.
Split:
"A,B,C".split(",")Produces:
['A', 'B', 'C']Join:
",".join(['A', 'B', 'C'])Produces:
"A,B,C"Shared Sequence Behavior
Both lists and strings support:
obj[0]
obj[-1]
obj[1:3]because both are ordered sequences.
Student Challenge
Challenge 1
Write a function called is_palindrome(word) that checks whether a given string is a palindrome (reads the same forwards and backwards, such as "level" or "radar").
- Convert
wordto lowercase using.lower()so the check is case-insensitive (e.g.,"Kayak"should be recognized as a palindrome). - Compare the cleaned string to its reversed version using string slicing (
word[::-1]). - Test your function by iterating over a list of words
["level", "python", "radar", "Kayak", "hello"]and printing whether each word is a palindrome.
Hint
This slice:
word[::-1]creates a reversed copy of the string.
No loop is required.
Click to see solution
# palindromes.py
def is_palindrome(word):
cleaned = word.lower()
return cleaned == cleaned[::-1]
words = ["level", "python", "radar", "Kayak", "hello"]
for word in words:
result = (
"palindrome"
if is_palindrome(word)
else "not a palindrome"
)
print(f"{word}: {result}")Challenge 2
Drive a route stored as a list of tuples, log each action as a string, and compare the planned route against where the robot actually ended up.
Real wheels slip a little, so final_position will not perfectly match a hand-calculated expected position.
Full sensor method details are available in the Python SDK reference.
Hint
get_position() can return:
Noneif odometry data has not yet arrived.
That’s why the challenge calls:
bot.wait_for_data()before requesting the position.
Click to see solution
# challenge_route_log.py
from bonicbot_bridge import BonicBot
route = [
("drive", 0.4),
("turn", 90),
("drive", 0.3),
("turn", -90),
]
action_log = []
with BonicBot(host='192.168.0.188') as bot:
bot.wait_for_data(timeout=3.0)
for action, value in route:
if action == "drive":
bot.drive_distance(value, speed=0.2)
action_log.append(f"drove {value} m")
elif action == "turn":
bot.rotate_angle(value, speed=45.0)
action_log.append(f"turned {value}°")
summary = "Route complete: " + ", ".join(action_log) + "."
print(summary)
final_position = bot.get_position()
print(f"Final position: {final_position}")Reflection Question
action_logis built up one string at a time inside the loop, then joined once at the end with", ".join(...). Why is joining once at the end generally better practice than repeatedly gluing each new piece onto a growing string with+=inside the loop?
By the end of this lesson, you should be able to create and modify lists, access elements with indexing and slicing, pair data using zip(), split and join strings, change text case, and combine list and string operations to produce formatted output.