Reputation: 1028
I'm learning Scala. Now I have this code snippet:
sealed abstract class BSTree {
def fold[A](init: A)(f: (A, Int) => A): A = this match {
case Empty => init
case Node(left, data, right) =>
val curInorder:A = f(left.fold(init)(f), data)
right.fold(curInorder)(f)
}
}
case object Empty extends BSTree
case class Node(left: BSTree, data: Int, right: BSTree) extends BSTree
My aim is to add another method toList in class BSTree
, which is on top
of method fold
and build a List
from the binary tree's inorder traversal.
My current implementation is:
sealed abstract class BSTree {
def fold[A](init: A)(f: (A, Int) => A): = .....//code snippet skipped
def toList: List[Int] =
fold(Nil: List[Int])((xs: List[Int], hd)=> hd::xs).reverse
}
But I feel that building a List
and then reversing it is ugly. Is there a more elegant approach?
Any hints are appreciated.
Upvotes: 0
Views: 847
Reputation: 5093
First of all, your fold is not tail recursive which for large input might result in StackOverflowException
. I'd encourage you to try out and implement it on your own using Stack
. For reference I'll place a sample implementation at the bottom of my post.
Secondly, as it was already mentioned in comments - you might want to use ListBuffer
so that building your list is more efficient in reversed order (thus, there is no need to reverse it back).
Here's a one-liner:
def toList: List[Int] = fold(ListBuffer.empty[Int])(_ += _).toList
And the the reference for implementing tail-recursive fold
:
def fold[B](init: B)(op: (B, A) => B): B = {
def go(stack: List[(A, Tree[A])], current: Tree[A], acc: B): B = (current, stack) match {
case (Empty, Nil) => acc
case (Empty, (d, r) :: xs) => go(xs, r, op(acc, d))
case (Node(l, d, r), _) => go((d, r) +: stack, l, acc)
}
go(Nil, this, init)
}
Upvotes: 1
Reputation: 780
I find that simply using xs :+ hd
instead of hd::xs
puts the values in the correct order (depth-first, left-to-right).
val testTree: BSTree =
Node(Node(Empty, 0, Empty), 1, Node(Empty, 2, Node(Node(Empty, 3, Empty), 4, Empty)))
def toList(bSTree: BSTree): List[Int] =
bSTree.fold(List[Int]())((acc, next) => acc :+ next)
toList(testTree) // List(0,1,2,3,4)
My implementation above is O(n²). We can improve it to O(n) by using a ListBuffer
, as per @dkim's comment, or we can use Vector
and then convert to List
when we're done.
Apart from simply fixing the toList
method, we might ask why the result of using fold
to implement toList
didn't agree with our intuition (giving us a backwards list instead of a forwards list). One might point out that the fold signature for list matches the structure of the List
class hierarchy.
abstract class List[+A] {
def fold[B](init: B)(step: (A, B) => B): B
}
case object Empty extends List[Nothing] {
def fold[B](init: B)(step: (A, B) => B): B = init
}
case class Cons[+A](head: A, tail: List[A]) extends List[A] {
def fold[B](init: B)(step: (A, B) => B): B =
step(head, tail.fold(init)(step))
}
Notice how the method signature of fold
matches the class hierarchy, even down to the values that each implementing class holds. (Aside: For purposes of brevity, I am using a very naive implementation of fold
that is neither efficient nor stack safe. Production implementations should be tail recursive or use a loop and a mutable buffer, but the point is that the method signature would be the same.)
We can do the same for your BSTree
class, the fold
signature would be:
abstract class BSTree {
def fold[A](withEmpty: A)(withNode: (A, Int, A) => A): A
}
Then toList
would be tree.fold(List[Int]())((l, n, r) => l ++ List(n) ++ r)
. But again, use a buffer or Vector
to get decent performance if you anticipate tree
being even about 50 entries or so.
Upvotes: 1