← Computer Programming I

Week 5 Lecture: Introduction to Lists

1. What is a List?

  • A list is a data structure that stores an ordered collection of items.
  • They solve the problem of needing to store many related values (like all student scores in a class) without creating a separate variable for each one.
  • Lists have two essential properties:
    • Ordered: The position of each item is preserved. The first item you add stays in the first position unless you move it.
    • Mutable: “Mutable” means “changeable.” You can add, remove, or change items in a list after it has been created.

Syntax

Lists are created using square brackets [], with items separated by commas.

# A list of integers
prime_numbers = [2, 3, 5, 7, 11, 13]

# A list of strings
class_roster = ["Alice", "Bob", "Charlie"]

# An empty list, ready to be filled later
upcoming_assignments = []

# A list can even hold mixed data types
student_profile = ["Alice", 21, 3.85, True]

Common Mistake: Using parentheses () or curly braces {} will create a different type of data structure. Always use square brackets [] for lists.

Example: Creating Lists

Problem: We need to track a student’s recent quiz scores and the subjects they correspond to.

Code:

# Create a list of integer quiz scores
quiz_scores = [88, 92, 100, 75, 95]

# Create a list of string subjects
subjects = ["Math", "Physics", "Chemistry", "History", "English"]

# Print the lists to the console to see their contents
print("Quiz Scores:", quiz_scores)
print("Subjects:", subjects)

Output:

Quiz Scores: [88, 92, 100, 75, 95]
Subjects: ['Math', 'Physics', 'Chemistry', 'History', 'English']

2. Accessing Elements: Indexing

To get a single item out of a list, you use its position, called an index.

  • The Golden Rule of Indexing: It’s Zero-Based!
    • The first item in a list is at index 0.
    • The second item is at index 1.
    • The last item is at index length - 1.
  • Negative Indexing: Python provides a convenient shortcut to access items from the end of the list.
    • -1 refers to the last item.
    • -2 refers to the second-to-last item, and so on.

Syntax

You access an item by putting its index in square brackets after the list variable’s name.

list_variable[index]

Important Note: The IndexError If you try to access an index that doesn’t exist (e.g., asking for index 5 in a list with only 5 items), Python will stop and give you an IndexError: list index out of range. This is one of the most common errors for beginners!

Example: Lap Times

Problem: Given a list of a runner’s lap times, we need to extract specific laps and calculate the difference between them.

Code:

lap_times = [45.5, 44.9, 46.1, 44.8, 45.2]

# Retrieve the first lap time (at index 0)
first_lap = lap_times[0]

# Retrieve the last lap time using negative indexing
last_lap = lap_times[-1]

# Calculate the difference between the second (index 1) and fourth (index 3) laps
# Note: an item from a list can be used in calculations just like any other variable.
difference = lap_times[3] - lap_times[1]

print(f"First lap time: {first_lap}")
print(f"Last lap time: {last_lap}")
print(f"Difference between lap 4 and lap 2: {round(difference, 2)} seconds")

Output:

First lap time: 45.5
Last lap time: 45.2
Difference between lap 4 and lap 2: -0.1 seconds

3. Processing Every Item: Looping Through Lists

Traversal (or iteration) is the process of visiting every element in a list, one by one, to perform an action. This is the most common way we work with lists. Python’s for loop provides a very clean and readable way to do this.

The Accumulator Pattern

This is a fundamental programming pattern used to calculate a cumulative value from a list (like a sum, average, or count).

  1. Initialize a variable (the “accumulator”) to a starting value (e.g., 0).
  2. Loop through the list.
  3. Accumulate: In each iteration, update the accumulator with the current item’s value.

Syntax

for temporary_variable in list_name:
    # This code block runs once for each item.
    # The temporary_variable will hold the current item's value.

Example: Calculating Total Expenses

Problem: Given a list of monthly expenses, calculate the total amount spent.

Code:

expenses = [1200.50, 345.00, 85.75, 201.00, 450.25]

# 1. Initialize an accumulator variable to zero
total_expenses = 0.0

# 2. Use a for loop to traverse the list
for expense in expenses:
    # 3. Add the current item's value to the accumulator
    total_expenses += expense # This is a shortcut for total_expenses = total_expenses + expense

# After the loop finishes, the total is complete
print(f"Total monthly expenses: ${total_expenses:.2f}")

Output:

Total monthly expenses: $2282.50

4. Changing a List: Mutability and List Methods

Because lists are mutable, we can change their contents after they’ve been created. Python provides built-in functions that “belong” to lists, called methods, to help us do this. You call a method using a dot (.) after the list variable.

Common List Methods for Modification

Method Description
list.append(item) Adds an item to the very end of the list.
list.insert(index, item) Inserts an item at a specific index, shifting other items over.
list.remove(value) Removes the first occurrence of a value. (Causes an error if not found).
list.pop(index) Removes the item at a given index and returns its value. If index is omitted, it removes the last item.
list[index] = new_value Replaces the item at an existing index with a new value.

Example: Managing a Party Guest List

Problem: We need to manage a dynamic guest list where people are added and removed.

Code:

# Start with an initial guest list
guests = ["Alice", "Bob", "Charlie"]
print(f"Initial guests: {guests}")

# A new guest, "David", confirms. Add him to the end.
guests.append("David")
print(f"After adding David: {guests}")

# A VIP guest, "Eve", needs to be at the start of the list (index 0).
guests.insert(0, "Eve")
print(f"After adding VIP Eve: {guests}")

# "Bob" cancels. Remove him by his name.
guests.remove("Bob")
print(f"After Bob cancels: {guests}")

# The last person to arrive has to leave.
guests.pop() # .pop() with no argument removes the last item
print(f"After the last person leaves: {guests}")
    
# We misspelled "Alice". Let's correct it by changing the value at index 1.
guests[1] = "Alicia" # The first guest 'Eve' is at index 0
print(f"After correcting a name: {guests}")

Output:

Initial guests: ['Alice', 'Bob', 'Charlie']
After adding David: ['Alice', 'Bob', 'Charlie', 'David']
After adding VIP Eve: ['Eve', 'Alice', 'Bob', 'Charlie', 'David']
After Bob cancels: ['Eve', 'Alice', 'Charlie', 'David']
After the last person leaves: ['Eve', 'Alice', 'Charlie']
After correcting a name: ['Eve', 'Alicia', 'Charlie']

5. Getting Portions of a List: Slicing

Slicing lets you create a new list that is a subset or “slice” of an original list.

  • Crucial Point: Slicing does not modify the original list. It always creates a new, smaller list.

Syntax: [start:stop:step]

  • start: The index where the slice begins (inclusive). If omitted, it defaults to the beginning (0).
  • stop: The index where the slice ends (exclusive). This is the most important part! The slice goes up to but does not include this index. If omitted, it defaults to the very end.
  • step: How many items to “jump” over. Defaults to 1.

Remember: my_list[0:3] gets the items at indices 0, 1, and 2, but not 3.

Common Slicing Shortcuts

  • my_list[:4] - Get all items from the beginning up to index 4 (exclusive).
  • my_list[2:] - Get all items from index 2 (inclusive) to the very end.
  • my_list[:] - Create a complete copy of the list.
  • my_list[::-1] - A clever trick to create a reversed copy of the list.

Example: Marathon Water Stations

Problem: From a list of all water stations in a marathon, create separate lists for the early stations, the late stations, and stations for runners who only want water every 10km.

Code:

water_stations = [5, 10, 15, 20, 25, 30, 35, 40]
print(f"Original stations: {water_stations}")

# 1. Get the first three stations (indices 0, 1, 2)
early_stations = water_stations[0:3]
print(f"Early stations: {early_stations}")

# 2. Get stations from 25km (index 4) to the end
late_stations = water_stations[4:]
print(f"Late stations: {late_stations}")

# 3. Get every other station using a step of 2
every_other_station = water_stations[::2]
print(f"Every other station: {every_other_station}")

# 4. Prove the original list was not changed by slicing
print(f"Original stations (unchanged): {water_stations}")

Output:

Original stations: [5, 10, 15, 20, 25, 30, 35, 40]
Early stations: [5, 10, 15]
Late stations: [25, 30, 35, 40]
Every other station: [5, 15, 25, 35]
Original stations (unchanged): [5, 10, 15, 20, 25, 30, 35, 40]


Practice Problems

These problems are designed to help you practice the concepts we learned about lists. They will require you to combine your knowledge of lists with concepts from previous weeks, such as functions, if/else statements, and loops.

Try to solve each problem on your own before looking at the solutions at the end of this page. The best way to learn is by trying, making mistakes, and debugging your own code!


Problem 1: Filtering High Scores

Problem Statement: Write a Python function called filter_high_scores that takes two arguments:

  1. A list of integers named scores.
  2. An integer named threshold.

The function should iterate through the scores list and create a new list containing only the scores that are greater than or equal to the threshold. The original scores list should not be modified. Finally, the function should return the new list of high scores.

Examples:

  • Calling filter_high_scores([88, 91, 75, 99, 82], 90) should return the list [91, 99].
  • Calling filter_high_scores([10, 25, 50, 75], 50) should return the list [50, 75].
  • Calling filter_high_scores([60, 70, 80], 90) should return an empty list [].

Problem 2: Applying a Grade Curve

Problem Statement: Write a Python function named curve_grades that modifies a list of student grades in-place (meaning you change the original list directly). The function should accept two arguments:

  1. A list of numbers (integers or floats) named grades.
  2. A number named curve_points representing the points to add to each grade.

Your function should iterate through the grades list and add curve_points to each grade. However, there is a maximum possible grade of 100. If adding the curve points would result in a grade over 100, the grade should be set to 100 exactly.

Since you are modifying the list in-place, your function should not return any value.

Example Behavior:

  • You start with a list test_scores = [85, 92, 77, 68, 100].
  • After you call curve_grades(test_scores, 5), the test_scores list itself should now be [90, 97, 82, 73, 100].

  • You start with a list final_exams = [95, 88, 98].
  • After you call curve_grades(final_exams, 7), the final_exams list should now be [100, 95, 100].

Problem 3: Reversing a List In-Place

Problem Statement: While you can easily reverse a list with the slice [::-1], understanding how to do it manually is a classic and important programming exercise.

Write a function called reverse_list_in_place that takes one argument: a list named data_list.

The function should reverse the elements of data_list in-place, meaning you should modify the original list directly without creating a new one. Because of this, the function should not return any value.

Hint: Use two index variables, one starting at the beginning of the list (left) and one at the end (right). In a loop, swap the elements at the two index positions, and then move the indices towards the center of the list (left moves forward, right moves backward). The loop should stop when the indices meet or cross each other.

Example Behavior:

  • You start with letters = ['a', 'b', 'c', 'd', 'e'].
  • After calling reverse_list_in_place(letters), the letters list should be ['e', 'd', 'c', 'b', 'a'].

  • You start with numbers = [1, 2, 3, 4].
  • After calling reverse_list_in_place(numbers), the numbers list should be [4, 3, 2, 1].

Solutions

Reminder: Please attempt the problems on your own before reviewing these solutions!

Solution for Problem 1: Filtering High Scores

def filter_high_scores(scores, threshold):
    """
    Creates a new list containing scores greater than or equal to a threshold.
    """
    # Create an empty list to store the results
    high_scores = []

    # Traverse the input list of scores
    for score in scores:
        # Check if the current score meets the condition
        if score >= threshold:
            # If it does, add it to our new list
            high_scores.append(score)

    return high_scores

# --- Testing the function ---
scores1 = [88, 91, 75, 99, 82]
threshold1 = 90
print(f"Scores above {threshold1}: {filter_high_scores(scores1, threshold1)}")

scores2 = [10, 25, 50, 75]
threshold2 = 50
print(f"Scores above {threshold2}: {filter_high_scores(scores2, threshold2)}")

Solution for Problem 2: Applying a Grade Curve

def curve_grades(grades, curve_points):
    """
    Modifies a list of grades in-place by adding a curve, capped at 100.
    """
    # We need the index to modify the list, so we use range(len()).
    for i in range(len(grades)):
        new_score = grades[i] + curve_points

        if new_score > 100:
            grades[i] = 100
        else:
            grades[i] = new_score
    # This function does not have a return statement.

# --- Testing the function ---
test_scores = [85, 92, 77, 68, 100]
print(f"Original scores: {test_scores}")
curve_grades(test_scores, 5) # Modify the list in-place
print(f"Curved scores:   {test_scores}")

final_exams = [95, 88, 98]
print(f"Original exams: {final_exams}")
curve_grades(final_exams, 7)
print(f"Curved exams:   {final_exams}")

Solution for Problem 3: Reversing a List In-Place

def reverse_list_in_place(data_list):
    """
    Reverses a list's elements in-place using the two-pointer technique.
    """
    # 'left' pointer starts at the first element (index 0)
    left_index = 0
    # 'right' pointer starts at the last element
    right_index = len(data_list) - 1

    # Loop until the pointers meet or cross in the middle
    while left_index < right_index:
        # Swap the elements at the left and right pointers
        # A common way to swap two variables in Python
        data_list[left_index], data_list[right_index] = data_list[right_index], data_list[left_index]

        # Move the pointers closer to the center
        left_index += 1
        right_index -= 1

# --- Testing the function ---
letters = ['a', 'b', 'c', 'd', 'e']
print(f"Original letters: {letters}")
reverse_list_in_place(letters)
print(f"Reversed letters: {letters}")

numbers = [1, 2, 3, 4]
print(f"Original numbers: {numbers}")
reverse_list_in_place(numbers)
print(f"Reversed numbers: {numbers}")