🔒
Pro Content
Subscribe to access all 69 OCR H446 A Level lessons.
£7.99/month
or £59/year
Subscribe now →
🔒 Pro · Component 2 · 2.2.1 Problem Solving & Programming
2.2.1c Sets, Maps and Graph Traversal in Code
OCR H446 · A Level Computer Science · ~18 min read
Notes
Video
Slides
Worksheet
Quiz

Sets in Code

A set is an unordered collection of unique elements — no duplicates. In Python, sets are written with curly braces {} or set(). Key property: membership testing is O(1) — very fast.

my_set = {1, 2, 3, 4, 5}
empty_set = set()   # Note: {} creates an empty DICT, not a set

# Basic operations
my_set.add(6)         # {1, 2, 3, 4, 5, 6}
my_set.remove(3)      # {1, 2, 4, 5, 6}  — raises KeyError if not found
my_set.discard(99)    # No error if not found
print(4 in my_set)    # True — O(1) lookup

Set Operations

A = {1, 2, 3, 4}
B = {3, 4, 5, 6}

union        = A | B    # {1, 2, 3, 4, 5, 6}  — all elements
intersection = A & B    # {3, 4}               — elements in both
difference   = A - B    # {1, 2}               — in A but not B
sym_diff     = A ^ B    # {1, 2, 5, 6}         — in one but not both

print(A.issubset({1, 2, 3, 4, 5}))  # True — A ⊆ {1..5}
print(A.issuperset({1, 2}))         # True — A ⊇ {1, 2}

When to Use Sets

  • Removing duplicates from a collection: list(set(my_list))
  • Fast membership testing: x in my_set is O(1) vs O(n) for lists
  • Set maths: union, intersection, difference for comparing collections
  • Tracking "visited" nodes in graph traversal (very common use case)

Maps / Dictionaries

A map (dictionary in Python) stores key-value pairs. Each key is unique; keys are immutable (strings, numbers, tuples). Values can be any type. Lookup by key is O(1) using a hash table.

student = {'name': 'Alice', 'grade': 'A', 'score': 95}

# Access
print(student['name'])          # 'Alice'
print(student.get('age', 0))   # 0 — default if key not found (no KeyError)

# Modify
student['grade'] = 'A*'        # update value
student['age'] = 17            # add new key-value pair
del student['score']           # remove key

# Iteration
for key, value in student.items():
    print(key, ':', value)

# Check key exists
if 'name' in student:
    print("Name:", student['name'])

Dictionary Methods

MethodReturns
d.keys()View of all keys
d.values()View of all values
d.items()View of (key, value) tuples
d.get(k, default)Value for k, or default if not found
d.pop(k)Removes and returns value for k
d.update(other)Merges other dict into d

Adjacency List using Dictionaries

Graphs can be stored as dictionaries where each key is a node and the value is a list (or set) of neighbours:

graph = {
    'A': ['B', 'C'],
    'B': ['A', 'D', 'E'],
    'C': ['A', 'F'],
    'D': ['B'],
    'E': ['B', 'F'],
    'F': ['C', 'E']
}
print(graph['B'])  # ['A', 'D', 'E'] — neighbours of B

Graph Traversal in Code

Graph traversal visits every reachable node starting from a given source. The two standard algorithms are BFS and DFS, implemented using a queue and stack respectively.

Breadth-First Search (BFS) Implementation

BFS visits nodes level by level. Uses a queue (FIFO). Finds the shortest path (in terms of edges) in an unweighted graph.

from collections import deque

def bfs(graph, start):
    visited = set()
    queue = deque([start])
    visited.add(start)
    order = []

    while queue:
        node = queue.popleft()     # dequeue from front (FIFO)
        order.append(node)

        for neighbour in graph[node]:
            if neighbour not in visited:
                visited.add(neighbour)
                queue.append(neighbour)  # enqueue to back

    return order

result = bfs(graph, 'A')   # ['A', 'B', 'C', 'D', 'E', 'F']

Depth-First Search (DFS) Implementation

DFS explores as deep as possible before backtracking. Can be implemented with an explicit stack or recursively (call stack acts as the stack).

# Iterative DFS using explicit stack
def dfs_iterative(graph, start):
    visited = set()
    stack = [start]
    order = []

    while stack:
        node = stack.pop()         # pop from top (LIFO)
        if node not in visited:
            visited.add(node)
            order.append(node)
            for neighbour in graph[node]:
                if neighbour not in visited:
                    stack.append(neighbour)

    return order

# Recursive DFS
def dfs_recursive(graph, node, visited=None):
    if visited is None:
        visited = set()
    visited.add(node)
    for neighbour in graph[node]:
        if neighbour not in visited:
            dfs_recursive(graph, neighbour, visited)
    return visited

BFS vs DFS Comparison

BFSDFS
Data structureQueue (FIFO — deque)Stack (LIFO — list or call stack)
Traversal orderLevel by level (breadth-first)As deep as possible first
Shortest pathYes — in unweighted graphsNot guaranteed
MemoryMore — stores all frontier nodesLess — stores one path at a time
Use casesShortest path, network broadcastsCycle detection, topological sort, maze solving
Exam tip: Know BFS uses a queue (deque.popleft()) and DFS uses a stack (list.pop()). The visited set prevents revisiting nodes in cycles. For BFS, add to visited WHEN enqueued (not dequeued) to avoid processing duplicates.
Exam tip: Set vs list — sets give O(1) membership testing; lists give O(n). For the visited set in graph traversal, ALWAYS use a set, never a list — otherwise the time complexity becomes O(n²).
✓ Notes completed!
Video coming soon
Click to advance · Arrow keys also work
Click slide or press arrow keys to navigate

Worksheet — 2.2.1c Sets, Maps & Graph Traversal in Code

8 questions · 24 marks · instantly marked

Q1State three properties of a set data structure and give two advantages over a list for membership testing.[5 marks]
✓ Mark scheme
Three properties (any 3): elements are unordered (no fixed position) [1]; elements are unique (no duplicates) [1]; elements must be hashable/immutable [1]; supports set operations (union, intersection, difference) [1]. Two advantages over list for membership testing: O(1) lookup time (hash-based) vs O(n) for list [1]; no need to iterate through all elements [1].
Q2Given A = {1, 2, 3, 4, 5} and B = {3, 4, 5, 6, 7}, write the result of: A | B, A & B, A - B, A ^ B.[4 marks]
✓ Mark scheme
A | B (union): {1, 2, 3, 4, 5, 6, 7} — all elements from both sets [1]. A & B (intersection): {3, 4, 5} — elements in both [1]. A - B (difference): {1, 2} — in A but not in B [1]. A ^ B (symmetric difference): {1, 2, 6, 7} — in exactly one set but not both [1].
Q3What is the difference between set.remove(x) and set.discard(x)?[2 marks]
✓ Mark scheme
remove(x): removes element x from the set; raises KeyError if x is not in the set [1]. discard(x): removes element x from the set; does nothing (no error) if x is not in the set [1]. Use discard when you're not sure if x is present and don't want to handle KeyError.
Q4Write Python code to represent the following graph as an adjacency list dictionary: A connects to B and C; B connects to D; C connects to D and E; D connects to E.[3 marks]
✓ Mark scheme
graph = { [1 — dictionary structure used]
    'A': ['B', 'C'],
    'B': ['A', 'D'],    # or just ['D'] if directed
    'C': ['A', 'D', 'E'],
    'D': ['B', 'C', 'E'],
    'E': ['C', 'D']
} [1 — all nodes present as keys; 1 — correct neighbours for each]. Note: if directed (one-way), B may not need to list A as a neighbour — either interpretation is valid if stated.
Q5Write a BFS function in Python that takes a graph (as an adjacency list dictionary) and a start node, and returns the order in which nodes are visited.[5 marks]
✓ Mark scheme
from collections import deque [0.5 — imports deque]
def bfs(graph, start): [0.5 — correct signature]
    visited = set() [1 — uses a set for visited]
    queue = deque([start]) [1 — queue initialised with start]
    visited.add(start)
    order = []
    while queue: [1 — loop until queue empty]
        node = queue.popleft()     # FIFO — popleft not pop
        order.append(node)
        for n in graph[node]:
            if n not in visited:
                visited.add(n)
                queue.append(n)
    return order [1 — returns traversal order]
Key: popleft() not pop() (FIFO). Visited set prevents cycles.
Q6Explain the difference between BFS and DFS in terms of: (a) data structure used, (b) traversal order, (c) ability to find shortest paths.[3 marks]
✓ Mark scheme
(a) BFS uses a queue (FIFO — deque.popleft()); DFS uses a stack (LIFO — list.pop()) or the call stack (recursive) [1]. (b) BFS visits level by level (all neighbours before their neighbours); DFS goes as deep as possible along one path before backtracking [1]. (c) BFS finds the shortest path (fewest edges) in unweighted graphs; DFS does not guarantee shortest paths [1].
Q7Why is a set rather than a list used for the 'visited' collection in BFS and DFS implementations?[2 marks]
✓ Mark scheme
Membership testing in a set is O(1) — the check 'if node not in visited' is constant time regardless of how many nodes are visited [1]. For a list, the same check is O(n) — it must scan the entire list. In a graph with many nodes, this degrades BFS/DFS from O(V+E) to O(V² + E), a significant slowdown [1].
Q8Write Python code using a dictionary to count how many times each word appears in the string: "the quick brown fox jumps over the lazy fox".[4 marks]
✓ Mark scheme
text = "the quick brown fox jumps over the lazy fox" [0.5]
words = text.split() [0.5 — splits into list of words]
counts = {} [1 — initialises empty dictionary]
for word in words: [1 — iterates over words]
    counts[word] = counts.get(word, 0) + 1 [1 — increments count, using get with default 0]
print(counts)
Result: {'the': 2, 'quick': 1, 'brown': 1, 'fox': 2, 'jumps': 1, 'over': 1, 'lazy': 1}
Alternative: if word in counts: counts[word]+=1; else: counts[word]=1 — also acceptable.
Topic Quiz
1 of 15
You scored
out of 15
🎯

Mini Test — 2.2.1c Sets, Maps & Graph Traversal

  • 10 questions · 10 marks · 10 minutes
  • 5 MCQ + 5 short answer
Card 1 of 15
Click to reveal
🎉
Complete!
TermDefinition
← 2.2.1b File & Exception Handling 2.2.1 Problem Solving & Programming Next: 2.2.1d Computational Methods →