---
title: "Immutable Traversals with Unfold"
date: 2022-11-12T21:27:42-05:00
draft: false
tags: ["Functional Programming", "Scala"]
math: false
---

Let's consider the following binary tree:

```goat
                   a                                                                       
                  / \                          
                 /   \         
                b     d                    
                 \        
                  \       
                   c       
```





We can encode this with the following Scala code:

```scala
final case class BinNode(
    val label: String,
    val left: Option[BinNode],
    val right: Option[BinNode]
)

// Leaf Nodes
val c_node = BinNode("c", None, None)
val d_node = BinNode("d", None, None)
// Rest of nodes
val b_node = BinNode("b", None, Some(c_node))
val a_node = BinNode("a", Some(b_node), Some(d_node))
```

For depth first search, an intuitive immutable implementation would be a recursive function.

```scala
// Using Preorder traversal
def DFS(node: BinNode): Iterator[BinNode] = 
    lazy val left_side = node.left.fold(Iterator.empty[BinNode])(DFS)
    lazy val right_side = node.right.fold(Iterator.empty[BinNode])(DFS)
    Iterator(node) ++ left_side ++ right_side
```

Let's evaluate this using our example above:

```scala
DFS(a_node).toList.map(_.label)
// List(a, b, c, d)
```

The recursive implementation inherently uses the system stack to keep track of the nodes.  This means that the last element gets evaluated in each step. Otherwise called last-in-first-out (LIFO). Breadth first search, however, uses a queue based approach where the first one added to the data structure is the first one considered (FIFO). 

To preserve immutability in our code, we can use `unfold`. Here our state is the queue of nodes.

```scala
def BFS(node: BinNode): Iterator[BinNode] =
    Iterator.unfold(List(node))(q =>
        if q.isEmpty then
            None
        else
            val crnt_node = q.head
            val next_q = q.tail ++ crnt_node.left ++ crnt_node.right
            Some(crnt_node, next_q)
    )
```

Evaluating on our example:

```scala
BFS(a_node).toList.map(_.label)
// List(a, b, d, c)
```

We can also use `unfold` for the depth first search approach as well. We can replace the list used with a stack.

```scala
import scala.collection.mutable.Stack
def DFS2(node: BinNode): Iterator[BinNode] = 
    Iterator.unfold(Stack(node))(s =>
        if s.isEmpty then
            None
        else
            val crnt_node = s.pop()
            s.pushAll(crnt_node.right)
            s.pushAll(crnt_node.left)
            Some(crnt_node, s)
    )
```

Using a stack introduces some mutability. We can use the immutable list data structure instead, as long as we satisfy the LIFO ordering.

```scala
def DFS3(node: BinNode): Iterator[BinNode] = 
    Iterator.unfold(List(node))(s =>
        if s.isEmpty then
            None
        else
            val crnt_node = s.last
            val next_s = s.init ++ crnt_node.right ++ crnt_node.left
            Some(crnt_node, next_s)
    )
```



