Building a tree from a stream with Scala

I want to build a tree that reads from a file of arbitrary height in this exact format,

       1
      2 3
     4 5 6
    . . . .
   . . . . .

      

using the following structure

case class Tree[+T](value : T, left :Option[Tree[T]], right : Option[Tree[T]])

      

The challenge I am facing is to read to the last line before I can build the tree, because left

and right

read only. I have tried,

  • Read the first line, create a node with value (1) with left

    and right

    set to None

    .
  • Read the second line, create nodes with values ​​(2) and (3), left

    and right

    set to None

    . This time a new node (1) is created with left

    = node (2) and right

    = node (3).
  • Read the third line, create nodes with values ​​(4), (5) and (6), with left

    and right

    set to None

    . Create new node (2) and node (3) using node (2) → node (4) and node (5) and node (3) → node (5) and node (6) and finally node (1) -> new node (2) and node (3)
  • Repeat to the end of the line.

At the end of this I must have this attitude,

         1
        /  \
       2    3
      / \   / \
     4   5 5  6
    / \ /\ /\ / \
   .  .. . .. .  .

      

Any good advice for me? Thanks to

+3


source to share


2 answers


Solution 1

This solution works recursively and requires that all lines have already been read. You are always in the same row. If there are more lines, you create Tree

for this purpose. Basically it will create Tree

for the leaves first.

When you have them, you know that the first and the second Tree

form a new one Tree

. The second and third form a second new Tree

, etc. This is what it does sliding

.

As an example for sliding: List(1,2,3,4).sliding(2) = List(List(1,2),List(2,3),List(3,4))

In this solution, I did not pay attention to efficiency. The result is Option[Tree]

. None

is given when there is no value / no line at all. It does not deal with cases where the string might be garbled.

case class Tree[+T](value : T, left :Option[Tree[T]], right : Option[Tree[T]])

def buildTree[T](lines: IndexedSeq[IndexedSeq[T]]) = {
  def recurse[T](lines: IndexedSeq[IndexedSeq[T]]): IndexedSeq[Tree[T]] = lines match {
    case line +: IndexedSeq() => line.map(Tree(_, None, None))
    case line +: rest => {
      val prevTrees = recurse(rest)
      (line, prevTrees.sliding(2).toIndexedSeq).zipped
      .map{case (v, IndexedSeq(left, right)) => Tree(v, Some(left), Some(right))}
    }
    case _ => IndexedSeq.empty
  }
  recurse(lines).headOption
}

      

Example:

val input = """1
2 3
4 5 6""".stripMargin

val lines = input.lines.map(_.filterNot(_ == ' ').toIndexedSeq).toIndexedSeq
//Vector(Vector(1), Vector(2, 3), Vector(4, 5, 6))

val result = buildTree(lines)

      



that leads to:

Some(Tree(1,Some(Tree(2,Some(Tree(4,None,None)),Some(Tree(5,None,None)))),Some(Tree(3,Some(Tree(5,None,None)),Some(Tree(6,None,None))))))

Random Insider (please ignore): I like it somehow zipped

now

Solution 2

This is the solution as you described it. It goes down from top to bottom and updates the tree after every insert. It has to follow the head though, because when we want to insert leaves we have to change all parent nodes, this reference is not stored in Tree

. I must admit that it all became useless with all these Option

s.

def buildTree2[T](lines: Iterator[IndexedSeq[T]]) = {
  @tailrec
  def loop(current: IndexedSeq[Tree[T]], head: Option[Tree[T]] = None): Option[Tree[T]] = {
    if(lines.hasNext) {
      val newTrees = lines.next.map(v => Option(Tree(v, None, None)))
      if(!current.isEmpty) {
        val h = (current, newTrees.sliding(2).toIndexedSeq).zipped.foldLeft(head.get) {
          case (nextHead, (parent, IndexedSeq(left, right))) => updateTree(nextHead, parent, Tree(parent.value, left, right))
        }
        loop(newTrees.flatten, Some(h))
      } else loop(newTrees.flatten, newTrees.head)
    } else head
  }

  def updateTree[T](head: Tree[T], target: Tree[T], replacement: Tree[T]): Tree[T] = {
    if(head eq target) {
      replacement
    } else head match {
      case Tree(v, Some(l), Some(r)) => Tree(v, Option(updateTree(l, target, replacement)), Option(updateTree(r, target, replacement)))
      case _ => head
    }
  }
  loop(IndexedSeq.empty)
}

      

We can now use it with an iterator.

val lines = input.lines.map(_.filterNot(_ == ' ').toIndexedSeq)
buildTree2(lines)

      

+5


source


The easiest is to just read the lines in reverse order (assuming you can fit the entire file into memory). Otherwise, your approach will work fine.



0


source







All Articles