Data Structures & Loops

Download notebook

Variables store single values. Data structures store collections of them. Python has three built-in types you’ll use constantly:

Lists

A list stores multiple items in order. Three things to know:

  • Ordered: Items stay in the order you put them. You access them by position (index).
  • Mutable: You can change, add, or remove items after creating the list.
  • Mixed types: A single list can hold strings, numbers, and other lists together.

Creating a List

Square brackets [], items separated by commas:

fruits = ["apple", "banana", "cherry", "date"]
print(fruits)
['apple', 'banana', 'cherry', 'date']

Accessing Elements

Each item has a position called an index. Indexing starts at 0 (not 1):

print(fruits[0])  # apple
print(fruits[1])  # banana
apple
banana

Negative indices count from the end. fruits[-1] gives you the last item:

print(fruits[-1])  # date
print(fruits[-2])  # cherry
date
cherry

Modifying a List

  1. Adding elements with append():

    fruits.append("elderberry")
    print(fruits)
    ['apple', 'banana', 'cherry', 'date', 'elderberry']
  2. Removing elements with remove() (removes the first match):

    fruits.remove("banana")
    print(fruits)
    ['apple', 'cherry', 'date', 'elderberry']
  3. Changing an element by assigning to its index:

    fruits[0] = "apricot"
    print(fruits)
    ['apricot', 'cherry', 'date', 'elderberry']

Looping Through a List

A for loop runs the same code for each item in a list:

for fruit in fruits:
    print(f"I love {fruit}!")
I love apricot!
I love cherry!
I love date!
I love elderberry!

The variable name after for (here, fruit) is your choice. Python assigns each list item to it in turn. As with if/else blocks, the indented lines are the loop body.

Notewhile loops

Python also has while loops, which keep running as long as a condition is True. These are useful when you don’t know in advance how many iterations you need. We won’t use them in this lesson, but they’re worth knowing about. You can read more in the Python docs.

Checking Membership

The in keyword checks whether an item exists in a list. After the modifications above, our list looks like this:

print(fruits)
['apricot', 'cherry', 'date', 'elderberry']
"cherry" in fruits
True
"guava" in fruits
False

You’ll usually combine in with if/else:

if "cherry" in fruits:
    print("cherry is in the list!")
cherry is in the list!
if "kiwi" in fruits:
    print("Found kiwi!")
else:
    print("No kiwi in this list.")
No kiwi in this list.
if "mango" not in fruits:
    print("We should add mango...")
We should add mango...

Built-in Functions for Lists

Function What it does Example
len() Number of items len([1, 2, 3]) → 3
sum() Sum of numeric items sum([10, 20]) → 30
min() Smallest item min([5, 3, 9]) → 3
max() Largest item max([5, 3, 9]) → 9
sorted() Returns a new sorted list sorted([3, 1, 2]) → [1, 2, 3]
numbers = [5, 3, 9, 1, 7]
print(len(numbers))
print(sum(numbers))
print(min(numbers), max(numbers))
print(sorted(numbers))
print(sorted(numbers, reverse=True))
5
25
1 9
[1, 3, 5, 7, 9]
[9, 7, 5, 3, 1]

Sets

A set stores unique items with no guaranteed order. If a list is a numbered sequence where position matters, a set is a bag where you only care about what’s inside.

  • Unordered: Items have no fixed position. You can’t access them by index.
  • Unique: Duplicates are automatically removed.
  • Mutable: You can add or remove items.

Creating a Set

Curly braces {} or the set() function. Notice how duplicates are removed automatically:

unique_numbers = {1, 2, 3, 4, 4, 2}
print(unique_numbers)
{1, 2, 3, 4}

You can also create a set from a list:

words = set(["apple", "banana", "apple", "cherry"])
print(words)
{'cherry', 'apple', 'banana'}

Modifying Sets

.add() inserts an element. .discard() removes one (without raising an error if it’s missing, unlike .remove()):

unique_numbers.add(5)
print(unique_numbers)
{1, 2, 3, 4, 5}
unique_numbers.discard(2)
print(unique_numbers)
{1, 3, 4, 5}

Set Operations

Sets support mathematical operations for comparing collections:

odds = {1, 3, 5, 7}
evens = {2, 4, 6, 8}
primes = {2, 3, 5, 7}

Union combines all elements from both sets:

print(odds | evens)
{1, 2, 3, 4, 5, 6, 7, 8}

Intersection keeps only elements in both sets:

print(odds & primes)
{3, 5, 7}

Difference keeps elements in the first set but not the second:

print(odds - primes)
{1}

Converting Between Lists and Sets

A common pattern: convert a list to a set to remove duplicates, then back to a list.

numbers = [1, 2, 2, 3, 4, 4, 5]
unique = list(set(numbers))
print(unique)
[1, 2, 3, 4, 5]

Dictionaries

A dictionary stores key-value pairs. You look up values by their key, like looking up a word in an actual dictionary.

student = {
    "name": "Alice",
    "age": 25,
    "major": "Computer Science"
}
print(student)
{'name': 'Alice', 'age': 25, 'major': 'Computer Science'}

Keys must be unique and are typically strings or integers. Dictionaries are mutable.

Accessing Values

Use square brackets or .get():

print(student["name"])
print(student.get("age"))
Alice
25

The difference: student["gpa"] raises a KeyError if the key doesn’t exist. student.get("gpa") returns None instead.

# print(student["gpa"])  # would raise KeyError
print(student.get("gpa"))
None

Modifying a Dictionary

Update an existing value or add a new key by assigning to it:

student["age"] = 35
print(student)
{'name': 'Alice', 'age': 35, 'major': 'Computer Science'}
student["gpa"] = 3.8
print(student)
{'name': 'Alice', 'age': 35, 'major': 'Computer Science', 'gpa': 3.8}

Remove entries with del or .pop(). .pop() also returns the removed value:

del student["major"]
print(student)
{'name': 'Alice', 'age': 35, 'gpa': 3.8}
gpa = student.pop("gpa")
print(f"Removed GPA: {gpa}")
Removed GPA: 3.8

Looping Through a Dictionary

Loop over keys:

for key in student:
    print(key)
name
age

Loop over values with .values():

for value in student.values():
    print(value)
Alice
35

Loop over both with .items() (note the two variables):

for key, value in student.items():
    print(f"{key}: {value}")
name: Alice
age: 35

Check if a key exists with in:

if "major" in student:
    print("Major is stored in the dictionary.")
else:
    print("Major is not in the dictionary.")
Major is not in the dictionary.
NoteWhat about tuples?

Python has another data structure called a tuple, written with parentheses: (1, 2, 3). Tuples behave like lists but are immutable (you can’t change their contents after creation). You’ll see them returned by functions like enumerate() and zip() in the bonus section below. We won’t cover them in depth here, but it’s useful to recognise the syntax when you encounter it.

Bonus: More Loop Techniques

These patterns come up often enough that they’re worth knowing now.

Indexed Looping with enumerate()

enumerate() gives you both the index and the value on each iteration:

fruits = ["apple", "banana", "cherry", "date"]

for index, fruit in enumerate(fruits):
    print(f"{index}: {fruit}")
0: apple
1: banana
2: cherry
3: date

Looping Multiple Lists with zip()

zip() pairs up items from two or more lists:

names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]

for name, age in zip(names, ages, strict=True):
    print(f"{name} is {age} years old")
Alice is 25 years old
Bob is 30 years old
Charlie is 35 years old

Use strict=True to catch mismatched list lengths (requires Python 3.10+, which Colab provides).

Compound Assignment Operators

These combine arithmetic with assignment. You’ll see them in loops that accumulate values:

x = 10
x += 5  # same as x = x + 5
print(x)
15
numbers = [1, 2, 3, 4, 5]
total = 0

for num in numbers:
    total += num

print(f"Total: {total}")
Total: 15

The full set of compound operators:

Operator Equivalent To Description
+= x = x + y Adds and assigns
-= x = x - y Subtracts and assigns
*= x = x * y Multiplies and assigns
/= x = x / y Divides and assigns
//= x = x // y Floor divides and assigns
%= x = x % y Modulus and assigns
**= x = x ** y Exponentiates and assigns

Putting It Together: Skyline Bar Chart

Here’s a small example that combines dictionaries, loops, and string operations to draw a text-based bar chart of building heights in Madrid.

# heights in metres (approximate)
buildings = {
    "Torre Cepsa": 55,
    "Torre de Cristal": 64,
    "Torre PwC": 40,
    "Torre Emperador": 46,
    "Torrespaña": 42,
}

# find the tallest so we can scale the bars to fit
max_height = max(buildings.values())

for name, height in buildings.items():
    bar_length = int(height / max_height * 20)  # scale to 20 characters wide
    bar = "█" * bar_length
    print(f"{name:>20}  {bar} {height}m")
         Torre Cepsa  █████████████████ 55m
    Torre de Cristal  ████████████████████ 64m
           Torre PwC  ████████████ 40m
     Torre Emperador  ██████████████ 46m
          Torrespaña  █████████████ 42m

Nothing here is new. The dictionary stores the data, the loop iterates through it, f-strings format the output, and string repetition ("█" * n) draws the bars. But combining them produces something visual and satisfying.

Challenges

1. Reverse a List

Reverse the list [1, 2, 3, 4].

lst = [1, 2, 3, 4]
lst.reverse()
print(lst)
[4, 3, 2, 1]

Or use slicing to create a reversed copy:

lst = [1, 2, 3, 4]
print(lst[::-1])
[4, 3, 2, 1]

2. Sum of Even Numbers

Find the sum of all even numbers in the list [1, 2, 3, 4, 5, 6].

lst = [1, 2, 3, 4, 5, 6]
total = 0

for num in lst:
    if num % 2 == 0:
        total += num

print(total)
12

3. Merge Lists Without Duplicates

Merge [1, 2, 3] and [2, 3, 4] into a single list with no duplicates.

lst_1 = [1, 2, 3]
lst_2 = [2, 3, 4]

merged = list(set(lst_1) | set(lst_2))
print(merged)
[1, 2, 3, 4]

Or more concisely:

print(list(set([1, 2, 3] + [2, 3, 4])))
[1, 2, 3, 4]

4. Merge Two Dictionaries

Merge the following two dictionaries. If a key appears in both, sum the values.

dict_1 = {'a': 1, 'b': 2, 'c': 3}
dict_2 = {'b': 3, 'c': 4, 'd': 5}

Expected result: {'a': 1, 'b': 5, 'c': 7, 'd': 5}

dict_1 = {'a': 1, 'b': 2, 'c': 3}
dict_2 = {'b': 3, 'c': 4, 'd': 5}

merged = {}
for dct in [dict_1, dict_2]:
    for key, value in dct.items():
        if key not in merged:
            merged[key] = value
        else:
            merged[key] += value

print(merged)
{'a': 1, 'b': 5, 'c': 7, 'd': 5}

5. Count Element Frequency

Given the list [1, 2, 2, 3, 3, 3, 4], count how many times each element appears. The result should be a dictionary: {1: 1, 2: 2, 3: 3, 4: 1}

arr = [1, 2, 2, 3, 3, 3, 4]
counts = {}

for value in arr:
    if value in counts:
        counts[value] += 1
    else:
        counts[value] = 1

print(counts)
{1: 1, 2: 2, 3: 3, 4: 1}