I have written the following method to print an arbitrary arity tree structure. (deep first search)
def treeString[A](tree: Tree[A]): String = {
def DFS(t: Tree[A], acc: String = "", visited: List[String] = Nil, toClose: Int = 0): String = t match {
case Leaf(id, terminal) if !visited.contains(id) => {
acc + " " + terminal.name + "[" + id.toString + "] " + ")"*toClose
}
case Node(id, op, args @_*) if !visited.contains(id) => {
var state = acc + " " + op.name + "[" + id.toString + "] " + "("
var args_visited: List[String] = List(id)
for (i <- args.tail) {
state = DFS(i, state , visited ::: args_visited) + " , "
args_visited = args_visited ++ List(i.id)
}
DFS(args.head, state , visited ++ args_visited, toClose + 1)
}
case _ => acc
}
DFS(tree)
}
The scala compiler claims this function is not tail recursive
. However at the tail position i have the proper DFS(..)
call. All of the DFS(..)
calls made in the loop will be done in an iterative fashion, thus stack safe.
I understand that if the tree is infinitely deep a stack overflow will occur. Do we have a technique to deal with these cases?
I have to agree with @VictorMoroz. The problem is that your statement:
state = DFS(i, state , visited ::: args_visited) + " , "
is not in the tail position. It does create a new stack, thus, your method is not tail recursive anymore.
Without deep dive into your data structures, here the way to tail-recursive-traverse a tree in DFS manner.
The key is to use an explicit stack - here in form of a List.
This is correct. Basically, in the most programming languages, each method call reserve some of the stack memory. In simple words tail-recursion allow the compiler to ignore the previous state - do not need the previous stack.
Bare in mind that DFS can not ensure to find the global optima and should not be used if this should be fulfilled.
Postscriptum: The
@tailrec
annotation is a friendly hint on the compiler to check in compile time whether your method can be optimized or not (Scala do tail-call optimization by default).