In regular recursion, we would recurse over the top level elements of a list, string, or tuple.
There are other recursive data structures that have multiple levels, for example, trees.
A tree, like the UNIX file system, has a root node. Each node can contain terminals nodes, called "leaves", or other nodes or branches. These nodes are called children. The main node is the parent. For example, in a UNIX directory, .. refers to the parent directory and . refers to the child directory. If a UNIX program spawns a process, the new process is the child and the originating process is the parent. (In C, you use the fork
command to create a child process.)
The recursive function must process each node and all the children nodes.
Our first example procedure is maptree(proc, tree)
which creates a copy of the given tree, with each leaf node replaced by proc(leafnode)
The base case is when a node is empty, an integer, or some other leaf node.
The recursive case is a list, in which the function is called recursively on each element of the list, and returns a copy of the list with appropriate modifications.
The functions in this notebook are from recursion.py
list(map(str,[1,2,3,4]))
['1', '2', '3', '4']
list(map(lambda x: x*x , [1,2,3,4]))
[1, 4, 9, 16]
list(map(str, [1,2,[3,4]]))
['1', '2', '[3, 4]']
type([])
list
def maptree(proc,tree):
if tree == []:
return tree
if not type(tree) == type([]):
return proc(tree)
result = []
for c in tree:
result.append(maptree(proc, c))
return result
l = [1,2,[3,4,[5,6],'a', 'b', []]]
l
[1, 2, [3, 4, [5, 6], 'a', 'b', []]]
maptree(str, l) ## convert every leaf into a string
['1', '2', ['3', '4', ['5', '6'], 'a', 'b', []]]
def square(x):
if type(x) == int:
return x*x
else:
return x
maptree(square, l)
[1, 4, [9, 16, [25, 36], 'a', 'b', []]]
l
[1, 2, [3, 4, [5, 6], 'a', 'b', []]]
maptree(lambda x: (x,x), l)
[(1, 1), (2, 2), [(3, 3), (4, 4), [(5, 5), (6, 6)], ('a', 'a'), ('b', 'b'), []]]
type(1)
int
maptree(lambda x: x*x if type(x) == type(1) else x, l)
[1, 4, [9, 16, [25, 36], 'a', 'b', []]]
We will redefine maptree using a list comprehension
def lcmaptree(proc,tree):
if tree == []:
return tree
if not type(tree) == type([]):
return proc(tree)
return [lcmaptree(proc,x) for x in tree]
lcmaptree(str, l)
['1', '2', ['3', '4', ['5', '6'], 'a', 'b', []]]
lcmaptree(square, l)
[1, 4, [9, 16, [25, 36], 'a', 'b', []]]
lcmaptree(lambda x: (x,x), l)
[(1, 1), (2, 2), [(3, 3), (4, 4), [(5, 5), (6, 6)], ('a', 'a'), ('b', 'b'), []]]
lcmaptree(lambda x: x*x if type(x) == type(1) else x, l)
[1, 4, [9, 16, [25, 36], 'a', 'b', []]]
Here is a nested list comprehension version of maptree.
def lclcmaptree(proc,tree):
return [[lclcmaptree(proc,x) for x in t] if type(t) == list
else t if t == [] else proc(t) for t in tree]
lclcmaptree(str, l)
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) /tmp/ipykernel_653364/3129645351.py in <module> ----> 1 lclcmaptree(str, l) /tmp/ipykernel_653364/3375282614.py in lclcmaptree(proc, tree) 1 def lclcmaptree(proc,tree): ----> 2 return [[lclcmaptree(proc,x) for x in t] if type(t) == list 3 else t if t == [] else proc(t) for t in tree] /tmp/ipykernel_653364/3375282614.py in <listcomp>(.0) 1 def lclcmaptree(proc,tree): ----> 2 return [[lclcmaptree(proc,x) for x in t] if type(t) == list 3 else t if t == [] else proc(t) for t in tree] /tmp/ipykernel_653364/3375282614.py in <listcomp>(.0) 1 def lclcmaptree(proc,tree): ----> 2 return [[lclcmaptree(proc,x) for x in t] if type(t) == list 3 else t if t == [] else proc(t) for t in tree] /tmp/ipykernel_653364/3375282614.py in lclcmaptree(proc, tree) 1 def lclcmaptree(proc,tree): ----> 2 return [[lclcmaptree(proc,x) for x in t] if type(t) == list 3 else t if t == [] else proc(t) for t in tree] TypeError: 'int' object is not iterable
Oops. This does not quite work. I invite you to fix it
We can also use the map function to traverse trees.
def mapmaptree(proc,tree):
if tree == []:
return tree
if not type(tree) == type([]):
return proc(tree)
return list(map(lambda x: mapmaptree(proc,x),tree))
mapmaptree(str, l)
['1', '2', ['3', '4', ['5', '6'], 'a', 'b', []]]
mapmaptree(square, l)
[1, 4, [9, 16, [25, 36], 'a', 'b', []]]
mapmaptree(lambda x: (x,x), l)
[(1, 1), (2, 2), [(3, 3), (4, 4), [(5, 5), (6, 6)], ('a', 'a'), ('b', 'b'), []]]
mapmaptree(lambda x: x*x if type(x) == type(1) else x, l)
[1, 4, [9, 16, [25, 36], 'a', 'b', []]]
Yet another attempt. This time I combine map with recursion.
def rmaptree(proc,tree):
if tree == []:
return tree
if not type(tree) == type([]):
return proc(tree)
result = []
result.append(rmaptree(proc, tree[0]))
result.extend(rmaptree(proc, tree[1:]))
return result
rmaptree(str, l)
['1', '2', ['3', '4', ['5', '6'], 'a', 'b', []]]
rmaptree(square , l)
[1, 4, [9, 16, [25, 36], 'a', 'b', []]]
rmaptree(lambda x: (x,x), l)
[(1, 1), (2, 2), [(3, 3), (4, 4), [(5, 5), (6, 6)], ('a', 'a'), ('b', 'b'), []]]
rmaptree(lambda x: x*x if type(x) == type(1) else x, l)
[1, 4, [9, 16, [25, 36], 'a', 'b', []]]
We will now look at a deep recursive function that does not return a copy of the tree. It simply returns the number of leaf nodes in the tree.
def countleaves(tree):
if tree == []:
return 0
if not type(tree) == list:
return 1
else:
return sum(map(countleaves,tree))
countleaves([1,2,3,4])
4
countleaves([[[]]])
0
countleaves([[[1,2,3]]])
3
countleaves(['a',['b',['c','d']]])
4
def rcountleaves(tree):
if tree == []:
return 0
if not type(tree) == type([]):
return 1
else:
return rcountleaves(tree[0]) + rcountleaves(tree[1:])
rcountleaves([1,2,3,4])
4
rcountleaves([[[[[]]]]])
0
rcountleaves([[[1,2,3],[4,5,6]]])
6
def lccountleaves(tree):
if tree == []:
return 0
if type(tree) != list:
return 1
else:
return sum([lccountleaves(x) for x in tree])
lccountleaves([1,2,3])
3
lccountleaves([[[[1]]]])
1
lccountleaves([[1,2,3],[4,5,6],[[[]]]])
6
End of Deep Recursion notebook.