Combining Streams in Scala

July 31, 2013

Given an interface Stream:

trait Stream[A] {
  def head: A
  def tail: Stream[A]
}

Constant streams

Write the functions zeroes, ones, and twos, which return streams of values 0, 1, and 2:

def zeroes: Stream[Int] = ???
// 0 -> 0 -> 0 -> 0 -> ...

def ones: Stream[Int] = ???
// 1 -> 1 -> 1 -> 1 -> ...

def twos: Stream[Int] = ???
// 2 -> 2 -> 2 -> 2 -> ...

Natural numbers streams

Write the functions evens and odds, which return streams of the even natural numbers (0, 2, 4, ...) and the odd natural numbers (1, 3, 5, ...):

def evens: Stream[Int] = ???
// 0 -> 2 -> 4 -> 6 -> ...

def odds: Stream[Int] = ???
// 1 -> 3 -> 5 -> 7 -> ...

Combined streams

Write the function combine, which interleaves its argument streams in constant time:

def combine[A](ss: Stream[A] *): Stream[A] = ???
// combine(evens, odds) = 0 -> 1 -> 2 -> 3 -> ...

Solution

Here is one possible solution.

Constants streams

object ConstantsStreams {

  class ConstantsStream(value: Int) extends Stream[Int] {
    def head: Int = value
    def tail: Stream[Int] = this
  }

  def zeroes: Stream[Int] =
    new ConstantsStream(0)

  def ones: Stream[Int] =
    new ConstantsStream(1)

  def twos: Stream[Int] =
    new ConstantsStream(2)
}

Natural numbers streams

object NatsStreams {

  class ByTwosStream(value: Int) extends Stream[Int] {
    def head: Int = value
    def tail: Stream[Int] = new ByTwosStream(value + 2)
  }

  def evens: Stream[Int] =
    new ByTwosStream(0)

  def odds: Stream[Int] =
    new ByTwosStream(1)
}

Combined streams

object CombinedStreams {

  class InterleavedStream[A](ss: Stream[A] *) extends Stream[A] {
    def head: A = ss.head.head
    def tail: Stream[A] =
      new InterleavedStream((ss.tail :+ ss.head.tail):_*)
  }

  def combine[A](ss: Stream[A] *): Stream[A] =
    new InterleavedStream[A](ss:_*)
}

Demo

object Streams extends App {

  def peek[A](s: Stream[A]): String =
    s"${s.head} -> ${s.tail.head} -> ${s.tail.tail.head} -> ${s.tail.tail.tail.head} -> ..."

  println()

  print(
    s"""|Constants streams:
        |  zeroes = ${peek(ConstantsStreams.zeroes)}
        |    ones = ${peek(ConstantsStreams.ones)}
        |    twos = ${peek(ConstantsStreams.twos)}
        |""".stripMargin
  )

  println()

  print(
    s"""|Natural numbers streams:
        |  evens = ${peek(NatsStreams.evens)}
        |   odds = ${peek(NatsStreams.odds)}
        |""".stripMargin
  )

  println()

  print(
    s"""|Combined streams:
        |  combine(evens, odds) = ${peek(CombinedStreams.combine(NatsStreams.evens, NatsStreams.odds))}
        |""".stripMargin
  )
}

This file is literate Scala, and can be run using Codedown:

$ curl https://earldouglas.com/exercises/scala-streams.md |
  codedown scala | xargs -0 scala -nc -e

Constants streams:
  zeroes = 0 -> 0 -> 0 -> 0 -> ...
    ones = 1 -> 1 -> 1 -> 1 -> ...
    twos = 2 -> 2 -> 2 -> 2 -> ...

Natural numbers streams:
  evens = 0 -> 2 -> 4 -> 6 -> ...
   odds = 1 -> 3 -> 5 -> 7 -> ...

Combined streams:
  combine(evens, odds) = 0 -> 1 -> 2 -> 3 -> ...