Follow the shape
For nested data, handle one item and recurse on any nested item. The calls mirror the containers.
Leaf is the base
A value that is not a container is the base case. Every branch ends at a leaf.
When the data itself nests, recursion is the natural fit. A function that handles a piece and calls itself on the sub-pieces mirrors the shape.
About 3 min of reading
Before this new lesson, one small question about the lesson you just finished. Recall it, do not look it up.
Walk nested data with recursion.
Sum a nested list.
Before you run it, say out loud what it will print. Then open the answer.
Count leaves.
Before you run it, say out loud what it will print. Then open the answer.
Flatten a list.
Before you run it, say out loud what it will print. Then open the answer.
Count nested dict keys.
Before you run it, say out loud what it will print. Then open the answer.
Before you write anything, put these lines back in the right order. Click a line to move it up one step. This support step is easier than writing code from scratch, and it is here to make the last example stick.
Read the first example one line at a time. A line that prints shows what appears on screen; the rest set things up quietly.
For nested data, handle one item and recurse on any nested item. The calls mirror the containers.
A value that is not a container is the base case. Every branch ends at a leaf.
def total(a):
s = 0
for x in a:
if isinstance(x, list):
s += total(x)
else:
s += x
return s
print(total([1, [2, [3]]]))
A list item is summed by the same function. The nesting of data drives the nesting of calls.
def leaves(a):
n = 0
for x in a:
if isinstance(x, list):
n += leaves(x)
else:
n += 1
return n
print(leaves([1, [2, [3]]]))
Each leaf counts one; each list recurses. The shape of the data is the shape of the work.
def depth(a):
if not a:
return 1
return 1 + max(depth(x) if isinstance(x, list) else 0 for x in a)
print(depth([1, [2, [3]]]))
The deepest branch sets the answer. Recursion measures the nesting.
def total(a):
s = 0
for x in a:
if isinstance(x, list):
s += total(x)
else:
s += x
return s
print(total([]))
An empty list sums to zero. That base case ends the walk.
def count_keys(d):
n = len(d)
for v in d.values():
if isinstance(v, dict):
n += count_keys(v)
return n
print(count_keys({"a": {"b": 1}}))
A nested dictionary is walked the same way. The container type decides the recursion.
def flat(a):
out = []
for x in a:
if isinstance(x, list):
out += flat(x)
else:
out.append(x)
return out
print(flat([1, [2, [3]]]))
Flatten calls itself on each sublist. The result is one flat list of leaves.
def total(a):
s = 0
for x in a:
if isinstance(x, list):
s += total(x)
else:
s += x
return s
print(total([5]))
The function reads like the definition of nested data. That is the reason recursion fits.
def leaves(a):
n = 0
for x in a:
if isinstance(x, list):
n += leaves(x)
else:
n += 1
return n
print(leaves([7]))
A non-list item is a leaf, counted directly. Leaves are the base cases.
Each one is defined here on this site. Open the entry for the full definition and the other words it points at.