Chapter 4: Python Collections - Lists, Tuples, and Dictionaries#

In Python, we have several built-in data structures to organize and store multiple pieces of information:

  1. Lists [] - Ordered, mutable collections (can be changed)

  2. Tuples () - Ordered, immutable collections (cannot be changed)

  3. Dictionaries {} - Collections of key-value pairs

These core data structures form the backbone of Python programming and are essential for writing clear, efficient, and scalable code.

4.1 Lists#

Lists are ordered collections of items that can be changed (mutable). They’re perfect for storing sequences of related data.

Key Characteristics:

  • Ordered: Items have a specific position (index)

  • Mutable: You can add, remove, or change items

  • Allow duplicates: Same value can appear multiple times

  • Mixed types: Can store different data types together

# A list storing temperatures from an experiment
temperatures = [298.15, 300.10, 299.80]
list_1 = [1,2,3, 4]
list_2 = ['a', 'b', 'c', 'd']
list_3 = [1, 'a', True, [1,2,3]]
type(list_3)
list
# Basic List Examples
print("=== Creating Lists ===")
# Numbers
numbers = [1, 2, 3, 4, 5]
print(f"Numbers: {numbers}")
# Mutation of Lists
numbers[2] = 10
print(f"Mutated Numbers: {numbers}")


# Strings
fruits = ["apple", "banana", "cherry", "date"]
print(f"Fruits: {fruits}")

# Mixed data types
mixed_data = ["hello", 42, 3.14, True]
print(f"Mixed data: {mixed_data}")

# Allow duplicates
measurements = [5.2, 7.1, 5.2, 5.2]  # ✓ Same value appears 3 times
print(f"Measurements with duplicates: {measurements}")

# Empty list
empty_list = []
print(f"Empty list: {empty_list}")
=== Creating Lists ===
Numbers: [1, 2, 3, 4, 5]
Mutated Numbers: [1, 2, 10, 4, 5]
Fruits: ['apple', 'banana', 'cherry', 'date']
Mixed data: ['hello', 42, 3.14, True]
Measurements with duplicates: [5.2, 7.1, 5.2, 5.2]
Empty list: []
print(f"\n=== List Properties ===")
print(f"Number of fruits: {len(fruits)}")
print(f"Number of numbers: {len(numbers)}")
print(f"List type: {type(numbers)}")
=== List Properties ===
Number of fruits: 4
Number of numbers: 5
List type: <class 'list'>

4.1.1 List Indexing and Slicing#

Just like strings, lists support indexing and slicing to access individual items or ranges of items.

Indexing:

  • list[0] - First item

  • list[-1] - Last item

  • list[i] - Item at position i

print("=== List Indexing ===")
scores = [85, 92, 78, 96, 81]
         # 0  1    2  3   4
print(f"Scores: {scores}")
=== List Indexing ===
Scores: [85, 92, 78, 96, 81]
scores[-1]
81
scores = [85, 92, 78, 96, 81]
scores_duplicate = scores.copy()
print(scores_duplicate)
print(scores)

scores[0] = 100 #85
print(scores)

print(scores_duplicate)
[85, 92, 78, 96, 81]
[85, 92, 78, 96, 81]
[100, 92, 78, 96, 81]
[85, 92, 78, 96, 81]
scores = [85, 92, 78, 96, 81]
scores[1] = 100
print(scores)
[85, 100, 78, 96, 81]
print("=== List Indexing ===")
scores = [85, 92, 78, 96, 81]
print(f"Scores: {scores}")
=== List Indexing ===
Scores: [85, 92, 78, 96, 81]
print(f"\n=== Accessing Individual Items ===")
print(f"Highest score: {scores[3]}")
print(f"Lowest score: {scores[2]}")
=== Accessing Individual Items ===
Highest score: 96
Lowest score: 78
# Mutation
scores_duplicate = scores.copy()
print(f"original list: {scores}")
print(f"duplicate list: {scores_duplicate}")
scores_duplicate[1] = 0
print(f"Modified Scores: {scores_duplicate}")
original list: [85, 92, 78, 96, 81]
duplicate list: [85, 92, 78, 96, 81]
Modified Scores: [85, 0, 78, 96, 81]

Slicing:

  • list[start:end] - Items from start to end-1

  • list[:n] - First n items

  • list[n:] - Items from position n to end

scores
[85, 92, 78, 96, 81]
scores[0:2]
[85, 92]
print(f"\n=== List Slicing ===")
print(f"First 3 scores: {scores[:3]}")
print(f"Every other score: {scores[::2]}")
=== List Slicing ===
First 3 scores: [85, 92, 78]
Every other score: [85, 78, 81]
# Calculate average of first 3 scores
first_three = scores[:3]
average = sum(first_three) / len(first_three)
print(f"Average of first 3 scores: {average:.1f}")
Average of first 3 scores: 85.0

4.1.2 List Methods#

Lists have many useful methods for adding, removing, and manipulating data:

Adding Items:

  • .append(item) - Add item to end

  • .insert(index, item) - Insert item at specific position

  • .extend(iterable) - Add multiple items

numbers = [1, 2, 3]
print(f"Starting list: {numbers}")

numbers.append(4)
print(numbers)

numbers.insert(1, 10)
print(numbers)


numbers.append(5)
numbers.append(6)
numbers.append(7)
numbers.append(8)

# numbers.extend([5,6,7,8])
print(numbers)
Starting list: [1, 2, 3]
[1, 2, 3, 4]
[1, 10, 2, 3, 4]
[1, 10, 2, 3, 4, 5, 6, 7, 8]
# Start with a simple list of numbers
numbers = [1, 2, 3]
print(f"Starting list: {numbers}")

print(f"\n=== Adding Items ===")
# Add single item
numbers.append(4)
print(f"After append: {numbers}")

# Insert at specific position
numbers.insert(1, 10)
print(f"After insert: {numbers}")

# Add multiple items
numbers.extend([5, 6])
print(f"After extend: {numbers}")
Starting list: [1, 2, 3]

=== Adding Items ===
After append: [1, 2, 3, 4]
After insert: [1, 10, 2, 3, 4]
After extend: [1, 10, 2, 3, 4, 5, 6]

Removing Items:

  • .remove(item) - Remove first occurrence of item

  • .pop() - Remove and return last item

  • .pop(index) - Remove and return item at index

numbers = [1,2,3,4,5,2]
numbers
[1, 2, 3, 4, 5, 2]
numbers.remove(2)

numbers
[1, 3, 4, 5, 2]
last_value = numbers.pop()

print(last_value)
print(numbers)
2
[1, 3, 4, 5]
numbers = [1,2,3,4,5,2]
print(numbers)
numbers.pop(1)
print(numbers)
[1, 2, 3, 4, 5, 2]
[1, 3, 4, 5, 2]
print(f"\n=== Removing Items ===")
# Remove specific value (first occurrence)
numbers.remove(2)
print(f"After removing 2: {numbers}")

# Remove last item
last_item = numbers.pop()
print(f"Removed {last_item}, remaining: {numbers}")

# Remove item at specific index
second_item = numbers.pop(1)
print(f"Removed item at index 1: {second_item}, remaining: {numbers}")
=== Removing Items ===
After removing 2: [1, 3, 4, 5]
Removed 5, remaining: [1, 3, 4]
Removed item at index 1: 3, remaining: [1, 4]

Other Methods:

  • .count(item) - Count occurrences of item

  • .index(item) - Find index of first occurrence

  • .sort() - Sort list in place

  • .reverse() - Reverse list in place

numbers = [1,1,1,2,2,3,3,3,3]
numbers.count(3)
4
numbers.index(2)
3
numbers = [2,5,10,87,1]

numbers.reverse()
print(numbers)
[1, 87, 10, 5, 2]
print(f"\n=== List Information ===")
# Add some duplicates for counting
numbers.extend([1, 3, 1])
print(f"List with duplicates: {numbers}")
print(f"Count of 1: {numbers.count(1)}")
print(f"Index of 3: {numbers.index(3)}")

print(f"\n=== Sorting and Reversing ===")
print(f"Before sorting: {numbers}")

# Sort in ascending order
numbers.sort()
print(f"After sorting: {numbers}")

# Reverse order
numbers.reverse()
print(f"After reversing: {numbers}")
=== List Information ===
List with duplicates: [1, 87, 10, 5, 2, 1, 3, 1]
Count of 1: 3
Index of 3: 6

=== Sorting and Reversing ===
Before sorting: [1, 87, 10, 5, 2, 1, 3, 1]
After sorting: [1, 1, 1, 2, 3, 5, 10, 87]
After reversing: [87, 10, 5, 3, 2, 1, 1, 1]

4.2 Tuples#

Tuples are ordered collections of items that cannot be changed (immutable). They’re perfect for storing fixed data.

Key Characteristics:

  • Ordered: Items have a specific position (index)

  • Immutable: Cannot add, remove, or change items after creation 🔒 🔒 🔒

  • Allow duplicates: Same value can appear multiple times

  • Mixed types: Can store different data types together

  • Faster: More memory efficient than lists for fixed data

When to Use Tuples:

  • Fixed coordinates or dimensions

  • Configuration parameters that shouldn’t change

  • Function return values (multiple values)

  • Dictionary keys (since they’re immutable)

# A tuple storing fixed simulation parameters
simulation_params = (300.0, 1.0, "NVT")  # temperature (K), pressure (atm), ensemble
list_1 = [1,2,3,4]

list_1[1] = 100

list_1
[1, 100, 3, 4]
tuples_1 = (1,2,3,4)

print(tuples_1)
print(type(tuples_1))

print(tuples_1[1])
tuples_1[1] = 100
(1, 2, 3, 4)
<class 'tuple'>
2
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[28], line 7
      4 print(type(tuples_1))
      6 print(tuples_1[1])
----> 7 tuples_1[1] = 100

TypeError: 'tuple' object does not support item assignment
temperature_range = [100, 200] # min temp, max temp
###
###

temperature_range[0] = 50          

###
###

temperature_range[0]
50
point = (10, 20)

print(point[0])
print(point[1])
print(point[-1])
10
20
20
# Basic Tuple Examples
print("=== Creating Tuples ===")
# Coordinates
point = (10, 20)
print(f"Point coordinates: {point}")

# RGB color
color = (255, 0, 128)
print(f"RGB color: {color}")
=== Creating Tuples ===
Point coordinates: (10, 20)
RGB color: (255, 0, 128)
print(f"\n=== Tuple Properties ===")
print(f"Point length: {len(point)}")
print(f"Tuple type: {type(point)}")

print(f"\n=== Accessing Tuple Items ===")
print(f"X coordinate: {point[0]}")
print(f"Y coordinate: {point[1]}")
=== Tuple Properties ===
Point length: 2
Tuple type: <class 'tuple'>

=== Accessing Tuple Items ===
X coordinate: 10
Y coordinate: 20
# Person info
person = ("Alice", 25, "Engineer")
print(f"Person info: {person}")

print(f"Person info length: {len(person)}")
print(f"Name: {person[0]}")
print(f"Age: {person[1]}")
print(f"Job: {person[2]}")
Person info: ('Alice', 25, 'Engineer')
Person info length: 3
Name: Alice
Age: 25
Job: Engineer
print(f"\n=== Tuple Slicing ===")
numbers = (1, 2, 3, 4, 5, 6)
print(f"Numbers: {numbers}")
print(f"First 3: {numbers[:3]}")
print(f"Last 2: {numbers[-2:]}")

print(f"\n=== Single Item Tuples ===")
# Common mistake - this is NOT a tuple!
not_a_tuple = (42)
print(f"not_a_tuple: {not_a_tuple}, type: {type(not_a_tuple)}")

# Correct way - need the comma!
single_tuple = (42,)
print(f"single_tuple: {single_tuple}, type: {type(single_tuple)}")
=== Tuple Slicing ===
Numbers: (1, 2, 3, 4, 5, 6)
First 3: (1, 2, 3)
Last 2: (5, 6)

=== Single Item Tuples ===
not_a_tuple: 42, type: <class 'int'>
single_tuple: (42,), type: <class 'tuple'>

4.2.1 Tuple Immutability#

Immutability means tuples cannot be changed after creation.

What you CANNOT do:

  • Add items

  • Remove items

  • Change existing items

Why is this important?

Because tuples are immutable, they:

  • Prevent accidental modification

  • Clearly represent fixed data

  • Are more memory-efficient than lists

  • Can be safely used as dictionary keys

print("=== Tuple Immutability ===")
point = (10, 20)
print(f"Original point: {point}")

# Try to modify - this will cause an error!
try:
    point[0] = 15  # Try to change x coordinate
except TypeError as e:
    print(f"Error: {e}")
    print("✓ Confirmed: Tuples cannot be modified!")

point_list = list(point)
point_list[0] = 15
print(f"Modified point: {point_list}")
new_point = tuple(point_list)
print(f"Modified point via list conversion: {new_point}")
=== Tuple Immutability ===
Original point: (10, 20)
Error: 'tuple' object does not support item assignment
✓ Confirmed: Tuples cannot be modified!
Modified point: [15, 20]
Modified point via list conversion: (15, 20)

4.2.2 Tuple Operation#

What you CAN do:

  • Access items by index

  • Slice tuples

  • Count occurrences with .count()

  • Find index with .index()

  • Unpack tuples into variables

Tuple Unpacking is a very useful feature that lets you assign tuple values to multiple variables at once.

numbers = (1, 2, 2, 3, 2, 4)
numbers.index(2)
1
print(f"\n=== Tuple Methods ===")
numbers = (1, 2, 2, 3, 2, 4)
print(f"Numbers: {numbers}")
print(f"Count of 2: {numbers.count(2)}")
print(f"Index of 3: {numbers.index(3)}")
=== Tuple Methods ===
Numbers: (1, 2, 2, 3, 2, 4)
Count of 2: 3
Index of 3: 3
# Column headers in a database query result
columns = ("id", "name", "email", "created_at")
email_position = columns.index("email")  # Find which column has email
row_data = (123, "Alice", "alice@email.com", "2024-01-01")
email_value = row_data[email_position]  # Get email from data row
# CSV header row (often returned as tuple)
headers = ("date", "temperature", "humidity", "pressure")
temp_idx = headers.index("temperature")  # Which column is temperature?

Practical Examples#

While tuples are immutable, .count() and .index() are useful for analyzing fixed data structures like database

# Practical Example 1: Working with CSV Database Files
# Reading sensor data from a CSV file

print("=== Example 1: Sensor Data Analysis ===")
print("Scenario: Analyze temperature readings from lab sensors\n")

# Read CSV file (each row becomes a tuple when using certain CSV methods)
with open("sensor_data.csv", "r") as file:
    lines = file.readlines()
    
# Get header (column names) as tuple
header = tuple(lines[0].strip().split(','))
print(f"CSV Columns: {header}")

# Using .index() to find which column contains temperature
temp_column = header.index("temperature")
pressure_column = header.index("pressure")
print(f"Temperature is in column {temp_column}")
print(f"Pressure is in column {pressure_column}")

# Process data rows as tuples
print(f"\n--- Analyzing Data Rows ---")
data_rows = []
for line in lines[1:]:
    row_tuple = tuple(line.strip().split(','))
    data_rows.append(row_tuple)

# Extract temperatures using the index we found
temperatures = []
for row in data_rows:
    temp = float(row[temp_column])
    temperatures.append(temp)
    
print(f"Temperatures: {temperatures}")

# Use .count() to find stable readings
target_temp = 23.5
stable_count = temperatures.count(target_temp)
print(f"\nStable readings at {target_temp}°C: {stable_count} times")
print(f"Percentage of stable readings: {stable_count/len(temperatures)*100:.1f}%")
# Practical Example 2: Student Database Records
print("\n=== Example 2: Student Records Database ===")
print("Scenario: Query student information from database\n")

# Read student records
with open("student_records.csv", "r") as file:
    lines = file.readlines()

# Header as tuple
header = tuple(lines[0].strip().split(','))
print(f"Database schema: {header}")

# Using .index() to find column positions (like SQL column lookup)
name_col = header.index("name")
email_col = header.index("email")
major_col = header.index("major")
gpa_col = header.index("gpa")

print(f"\nColumn positions:")
print(f"  Name: column {name_col}")
print(f"  Email: column {email_col}")
print(f"  Major: column {major_col}")
print(f"  GPA: column {gpa_col}")

# Process student records as tuples (immutable database rows)
print(f"\n--- Querying Records ---")
students = []
for line in lines[1:]:
    student_record = tuple(line.strip().split(','))
    students.append(student_record)

# Find Chemical Engineering students using the major column index
print(f"\nChemical Engineering Students:")
chem_eng_count = 0
for student in students:
    if student[major_col] == "Chemical Engineering":
        print(f"  {student[name_col]} - GPA: {student[gpa_col]}")
        chem_eng_count += 1

print(f"\nTotal Chemical Engineering students: {chem_eng_count}")

# Extract all majors and count occurrences
majors = [student[major_col] for student in students]
chem_eng_total = majors.count("Chemical Engineering")
print(f"Verified count using .count(): {chem_eng_total}")

4.2.2 Tuple Unpacking#

Tuple unpacking allows you to assign multiple variables at once by extracting values from a tuple in order. This makes code more concise, readable, and less error-prone.

param = (100, 200, 300)

p1, p2, p3 = param
print(p1, p2, p3)

p1 = param[0]
p2 = param[1]
p3 = param[2] # or param[-1]
print(p1, p2, p3)
100 200 300
100 200 300
len(param)
3
p1, p2, p3 = param
def custom_function():
    return 100, 200, 300

r1, r2, r3 = custom_function()
print(r1)
print(r2)
print(r3)
# r1 = result[0]
# r2 = result[1]
# r3 = result[2]
100
200
300
print(f"\n=== Tuple Unpacking (Very Useful!) ===")
# Unpack coordinates
point = (100, 200)
x, y = point
print(f"x = {x}, y = {y}")

# Unpack person info
person = ("Bob", 30, "Designer")
name, age, job = person
print(f"Name: {name}, Age: {age}, Job: {job}")
=== Tuple Unpacking (Very Useful!) ===
x = 100, y = 200
Name: Bob, Age: 30, Job: Designer
# Function returning multiple values
def get_name_and_score():
    return ("Alice", 95)

student_name, student_score = get_name_and_score()
print(f"Student: {student_name}, Score: {student_score}")
Student: Alice, Score: 95
print(f"\n=== Converting Between Lists and Tuples ===")
# List to tuple
my_list = [1, 2, 3, 4]
my_tuple = tuple(my_list)
print(f"List: {my_list}")
print(f"Tuple: {my_tuple}")

# Tuple to list (if you need to modify)
colors_tuple = ("red", "green", "blue")
colors_list = list(colors_tuple)
colors_list.append("yellow")
print(f"Original tuple: {colors_tuple}")
print(f"Modified list: {colors_list}")
=== Converting Between Lists and Tuples ===
List: [1, 2, 3, 4]
Tuple: (1, 2, 3, 4)
Original tuple: ('red', 'green', 'blue')
Modified list: ['red', 'green', 'blue', 'yellow']

4.3 Dictionaries#

Dictionaries are collections of key-value pairs that are mutable. They’re perfect for storing related information where you need to look up values by a meaningful name (key).

Key Characteristics:

  • Key-Value pairs: Each item has a key (name) and a value

  • Mutable: Can add, remove, or change items

  • Unique keys: Each key can only appear once

  • Fast lookup: Very efficient for finding values by key

Creating Dictionaries:

# Empty dictionary
data = {}

# Dictionary with values
person = {
    "name": "Alice",
    "age": 25,
    "city": "New York"
}

# Student grades
grades = {
    "math": 95,
    "science": 87,
    "english": 92
}

When to Use Dictionaries:

  • Storing properties of an object

  • Looking up values by name instead of position

  • Counting occurrences of items

  • Configuration settings

person = {
    "name": "Alice",
    "age": 25,
    "city": "New York"
}


person["name"]


result = person.get("salary", "No key")
print(result)
# person["salary"]
No key
person = ["Alice", 25, "New York", "Engineer"]
person[0]
print(person)
person = {
    "name": "Alice",
    "age": 25,
    "city": "New York",
    "job": "Engineer"
}
print(person["name"])
['Alice', 25, 'New York', 'Engineer']
Alice
person = {
    "name": "Alice",
    "age": 25,
    "city": "New York",
    "job": "Engineer"
}

# person['salary']
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
Cell In[63], line 8
      1 person = {
      2     "name": "Alice",
      3     "age": 25,
      4     "city": "New York",
      5     "job": "Engineer"
      6 }
----> 8 person['salary']

KeyError: 'salary'
# Person information
person = {
    "name": "Alice",
    "age": 25,
    "city": "New York",
    "job": "Engineer"
}
print(f"Person: {person}")
Person: {'name': 'Alice', 'age': 25, 'city': 'New York', 'job': 'Engineer'}
print(f"Name: {person['name']}")
print(f"Age: {person['age']}")

print(f"Person has {len(person)} properties")
Name: Alice
Age: 25
Person has 4 properties
# Compare with direct access
try:
    missing = person["salary"]  # This key doesn't exist
except KeyError as e:
    print(f"Error: Key {e} not found")
    print("✓ Use .get() method to avoid errors!")
Error: Key 'salary' not found
✓ Use .get() method to avoid errors!
print(f"\n=== Safe Access with .get() Method ===")
# Using .get() prevents errors if key doesn't exist
job = person.get("job", "Unknown")
salary = person.get("salary", "Not specified")

print(f"Job: {job}")
print(f"Salary: {salary}")
=== Safe Access with .get() Method ===
Job: Engineer
Salary: Not specified

4.3.1 Dictionary Methods and Operations#

Dictionaries have many useful methods for working with key-value pairs:

Adding/Modifying:

  • dict[key] = value - Add or update a key-value pair

  • .update(other_dict) - Add multiple key-value pairs

Removing:

  • del dict[key] - Remove key-value pair

  • .pop(key) - Remove and return value

  • .clear() - Remove all items

Accessing:

  • .keys() - Get all keys

  • .values() - Get all values

  • .items() - Get key-value pairs

  • .get(key, default) - Safe access with default

Checking:

  • key in dict - Check if key exists

person = {
    "name": "Alice",
    "age": 25,
    "city": "New York",
    "job": "Engineer",
}

key_list = list(person.keys())
value_list = list(person.values())

print(key_list)
print(value_list)

value_list[0]
['name', 'age', 'city', 'job']
['Alice', 25, 'New York', 'Engineer']
'Alice'
item_list = list(person.items())

first_name, first_value = item_list[0]

print(first_name, first_value)
name Alice
person = {
    "name": "Alice",
    "age": 25,
    "city": "New York",
    "job": "Engineer",
}


person["salary"] = 100_000
print(person)
#del person["salary"]

value = person.pop("salary")
print(value)
person
{'name': 'Alice', 'age': 25, 'city': 'New York', 'job': 'Engineer', 'salary': 100000}
100000
{'name': 'Alice', 'age': 25, 'city': 'New York', 'job': 'Engineer'}
person.keys()
dict_keys(['name', 'age', 'city', 'job'])
person.values()
dict_values(['Alice', 25, 'New York', 'Engineer'])
person.items()
dict_items([('name', 'Alice'), ('age', 25), ('city', 'New York'), ('job', 'Engineer')])
# Dictionary Methods Examples

print("=== Adding and Modifying Items ===")
student = {
    "name": "Bob",
    "age": 20,
    "major": "Computer Science"
}
print(f"Initial: {student}")

# Add new item
student["gpa"] = 3.8
print(f"After adding GPA: {student}")

# Modify existing item
student["age"] = 21
print(f"After birthday: {student}")

# Add multiple items
additional_info = {"year": "Junior", "credits": 75}
student.update(additional_info)
print(f"After update: {student}")

print(f"\n=== Removing Items ===")
# Remove specific item
gpa = student.pop("gpa")
print(f"Removed GPA: {gpa}")
print(f"Remaining: {student}")

# Delete item directly
del student["credits"]
print(f"After deleting credits: {student}")

print(f"\n=== Accessing Dictionary Data ===")
products = {
    "laptop": 999,
    "mouse": 25,
    "keyboard": 75,
    "monitor": 300
}

print(f"All products: {list(products.keys())}")
print(f"All prices: {list(products.values())}")

# Iterate through items
print(f"\nPrice list:")
for product, price in products.items():
    print(f"  {product}: ${price}")

print(f"\n=== Checking for Keys ===")
items_to_check = ["laptop", "tablet", "mouse"]

for item in items_to_check:
    if item in products:
        print(f"{item}: ${products[item]}")
    else:
        print(f"{item}: not available")

print(f"\n=== Practical Example: Word Counter ===")
text = "hello world hello python world"
words = text.split()

# Count word occurrences
word_count = {}
for word in words:
    if word in word_count:
        word_count[word] += 1
    else:
        word_count[word] = 1

print(f"Word counts: {word_count}")

# Alternative using .get()
word_count2 = {}
for word in words:
    word_count2[word] = word_count2.get(word, 0) + 1

print(f"Same result: {word_count2}")

4.4 Choosing the Right Data Structure#

Understanding when to use lists, tuples, or dictionaries:

Data Structure

When to Use

Example Use Cases

List []

• Ordered data that changes
• Need to add/remove items
• Same type of data

• Shopping list
• Test scores
• Todo items

Tuple ()

• Ordered data that’s fixed
• Coordinates or constants
• Multiple return values

• GPS coordinates
• RGB colors
• Database records

Dictionary {}

• Need to look up by name
• Key-value relationships
• Different types of properties

• Person info
• Settings/config
• Word counts

Decision Flow:

  1. Do you need to look up values by name? → Use Dictionary

  2. Will the data change after creation? → Use List

  3. Is the data fixed/constant? → Use Tuple

# Choosing the Right Data Structure - Examples

print("=== Example 1: Shopping List ===")
print("Scenario: Items you need to buy (order matters, items change)")

# ✅ Correct choice: List (changeable, ordered)
shopping_list = ["bread", "milk", "eggs"]
print(f"Shopping list: {shopping_list}")

# Easy to add/remove items
shopping_list.append("cheese")
shopping_list.remove("eggs")
print(f"Updated list: {shopping_list}")

print(f"\n=== Example 2: GPS Coordinates ===")
print("Scenario: Fixed location coordinates")

# ✅ Correct choice: Tuple (fixed data)
home_location = (40.7128, -74.0060)  # New York City
x, y = home_location
print(f"Home coordinates: ({x}, {y})")
print("Coordinates are protected from accidental changes")

print(f"\n=== Example 3: Student Information ===")
print("Scenario: Looking up student data by name")

# ✅ Correct choice: Dictionary (lookup by name)
students = {
    "Alice": {"age": 20, "major": "CS", "gpa": 3.8},
    "Bob": {"age": 21, "major": "Math", "gpa": 3.5},
    "Charlie": {"age": 19, "major": "Physics", "gpa": 3.9}
}

# Easy to look up by name
student = "Alice"
if student in students:
    info = students[student]
    print(f"{student}: {info['major']} major, GPA: {info['gpa']}")

# Easy to add new students
students["Diana"] = {"age": 20, "major": "Biology", "gpa": 3.7}
print(f"Added Diana. Total students: {len(students)}")

print(f"\n=== Complex Example: Combined Usage ===")
# Store multiple students with courses (using all three structures)

# Dictionary for student lookup
student_db = {}

# Add students with course lists and contact tuples
student_db["John"] = {
    "courses": ["Math", "Physics", "Chemistry"],  # List - courses change
    "contact": ("john@email.com", "555-1234"),   # Tuple - fixed contact info
    "grades": {"Math": 85, "Physics": 92}        # Dict - grade lookup
}

student_db["Mary"] = {
    "courses": ["Biology", "Chemistry"],
    "contact": ("mary@email.com", "555-5678"),
    "grades": {"Biology": 88, "Chemistry": 94}
}

# Display student information
for name, data in student_db.items():
    email, phone = data["contact"]  # Unpack tuple
    courses = ", ".join(data["courses"])  # Join list
    avg_grade = sum(data["grades"].values()) / len(data["grades"])
    
    print(f"\n{name}:")
    print(f"  Email: {email}, Phone: {phone}")
    print(f"  Courses: {courses}")
    print(f"  Average grade: {avg_grade:.1f}")
=== Example 1: Shopping List ===
Scenario: Items you need to buy (order matters, items change)
Shopping list: ['bread', 'milk', 'eggs']
Updated list: ['bread', 'milk', 'cheese']

=== Example 2: GPS Coordinates ===
Scenario: Fixed location coordinates
Home coordinates: (40.7128, -74.006)
Coordinates are protected from accidental changes

=== Example 3: Student Information ===
Scenario: Looking up student data by name
Alice: CS major, GPA: 3.8
Added Diana. Total students: 4

=== Complex Example: Combined Usage ===

John:
  Email: john@email.com, Phone: 555-1234
  Courses: Math, Physics, Chemistry
  Average grade: 88.5

Mary:
  Email: mary@email.com, Phone: 555-5678
  Courses: Biology, Chemistry
  Average grade: 91.0

Summary#

In this chapter, you learned about Python’s three main collection data structures:

Lists []#

  • Ordered and mutable collections

  • Perfect for sequences of data that change

  • Use for: shopping lists, scores, measurements

  • Key methods: .append(), .remove(), .sort(), .pop()

Tuples ()#

  • Ordered and immutable collections

  • Perfect for fixed data like coordinates

  • Use for: coordinates, constants, return values

  • Key methods: .count(), .index(), tuple unpacking

Dictionaries {}#

  • Key-value pairs, mutable

  • Perfect for looking up data by name

  • Use for: person info, settings, word counts

  • Key methods: .get(), .keys(), .values(), .items()

Quick Decision Guide#

  1. Need lookup by name? → Dictionary

  2. Data will change? → List

  3. Data is fixed? → Tuple

Practice: Try using these structures in your own programs to get comfortable with when to use each one!