This is the first of what I hope to be a series of posts in which I cover some common algorithms using purely functional programming. Needless to say there are numerous articles out there covering functional programming

and algorithms but material covering algorithm implementation in a purely functional style are rare and not easy to digest.

Tree traversal algorithms are a good place to start since both the data structure and the algorithms are easily described recursively. Consider the most common flavors of depth-first traversal of an n-ary tree.

- pre-order (node -> children )

- post-order (children -> node)

- in-order (first child -> node -> rest of children)

Note: Children are traversed left to right and in-order traversal makes a lot more sense when considering binary trees (where the number of children is ≤2)

A simple parametrized n-ary tree

Here’s a (somewhat) straightforward implementation of a pre-order traversal. The output will be a Queue[S] sorted according to the order of traversal.

The function f: T => S gives us the option to transform the value of type T if required.

foldLeft[B](z: B)(op: (B,A) => B): B traverses any data structure that it is defined on, and starting from an initial value of z , builds up a final value of type B by applying the function op(acc: B,n: A) at each element n of the data structure where into the accumulator acc

Let’s go over the function preOrder , which is simply a container for another function: loop: (Tree[T],Queue[S]) => Queue[S] which takes a Tree[T] and a Queue[S] as input and returns a Queue[S]

The input Queue is where the output of each step is enqueued at each recursive step. The initial call to loop passes the root node of the Tree and an empty set (which makes sense since we don’t have an output yet). At each iteration, loop traverses the children of the Tree passed to it and applies itself to each child node while passing the latest version of the output Queue starting with the initial value of f(v) (Basically the value of the root node). The effect of line 3 is then to start with the root node’s value and traverse each child sub-tree, appending the result to the Queue

A post-order traversal can be implemented similarly. Note that the initial value of foldLeft is the empty queue and the value v (or more precisely `f(v)`) is added to the queue after all the child trees have been evaluated.

post-order traversal

In-order traversal is slightly different since it is usually defined for binary trees where we first traverse the left subtree, then the node and finally the right subtree. In this case I am traversing the first child -> node -> rest of children which will behave just like an in-order traversal if we keep the size of the children trees to ≤2

Here’s a complete example of all three implementations for a Tree[Int] where f is identity (no transformations done).

This implementation has a major flaw… If you pass it very large trees as argument, it will cause a stack overflow. Consider this fact, in all the above traversal algorithms, traversing a Tree[T] involves traversing its child/children first and coming back to it once we’re done.

Since the children are evaluated before the parent, a LIFO data structure like a List would be ideal to store successive parent Tree s while we evaluate their children before coming back to them later. You can see a pre-order and post-order implementation that make use of a constant stack space below.

Constant Stack-Space implementations

The pre-order implementation is straightforward. We start with the root node as the only Tree in the List . At each stage, we enqueue the value at the current node and prepend the child nodes to the List ensuring the children of the current Tree always end up on top (if there are not children, nothing will be added to the tree but the current node will be evaluated and removed from the list). The post-order is slightly different in that we enqueue a node’s value (and remove it from the list) only when it has no children. Otherwise put its first child on top and put the current node (minus its first child) as the second element.

We can apply this approach to a more common variant of N-Ary trees, the Binary tree

Notice the left and right trees are optional.

As you can see, the functional approach allows us to write the code in a way that mirrors how the algorithm is described, i.e in terms of what needs to be done instead of how to do it in an imperative manner, and that in my opinion makes it easier to visualize and remember.