CodingNow
CodingNow

Reputation: 1028

Build List from binary tree inorder traversal using function "fold"

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

Answers (2)

bottaio
bottaio

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

Fried Brice
Fried Brice

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

Related Questions