Scala - Unfold



This chapter takes you through the concept of unfold in Scala programming. The unfold function is used to construct sequences and collections from a seed value. The function is applied repeatedly which produces the next state and value. It is the dual operation of fold. Fold reduces collection to a single value and unfold creates collection from a single value.

Unfold

The unfold function can be used to create collections, like, lists and streams. It generates subsequent elements based on a function that defines how to compute the next element and state. It is used to generate infinite sequences and continues until a termination condition is met.

Unfold is a technique of creating collection repeatedly. It generates the next state and value (from the previous pair) until a termination condition is met.

Syntax

The syntax of unfold function is -

def unfold[S, A](init: S)(f: S => Option[(A, S)]): List[A] = f(init) match {
   case Some((a, s)) => a :: unfold(s)(f)
   case None => Nil
}

Example of Unfold

The following example shows defining and using an unfold function in Scala programming -

object Demo {
   def unfold[S, A](init: S)(f: S => Option[(A, S)]): List[A] = f(init) match {
      case Some((a, s)) => a :: unfold(s)(f)
      case None => Nil
   }

   def main(args: Array[String]): Unit = {
      val list = unfold(0) { n =>
         if (n < 10) Some((n, n + 1))
         else None
      }
      println(list) 
   }
}

Save the above program in Demo.scala. Use the following commands to compile and execute this program.

Command

> scalac Demo.scala
> scala Demo

Output

List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

In the example, the unfold function generates a list of integers from 0 to 9 repeatedly.

Advantages of Unfold

You can generate collections based on custom rules for computing the next state and value. So it is used in various use cases. It promotes modular and reusable code. The unfold function can be used to generate infinite sequences. So it is for efficient and on-demand data generation.

Unfold with Streams

Streams are used to implement unfold functions. Streams are lazy lists, where elements are computed only when these are accessed.

Syntax

The syntax of unfold with streams is -

def unfoldStream[S, A](init: S)(f: S => Option[(A, S)]): Stream[A] = f(init) match {
   case Some((a, s)) => a #:: unfoldStream(s)(f)
   case None => Stream.empty
}

Example

Consider the example of generating an infinite stream of Fibonacci numbers using unfold in Scala programming -

object Demo {
   def unfoldStream[S, A](init: S)(f: S => Option[(A, S)]): Stream[A] = f(init) match {
      case Some((a, s)) => a #:: unfoldStream(s)(f)
      case None => Stream.empty
   }

   def main(args: Array[String]): Unit = {
      val fibs = unfoldStream((0, 1)) { case (a, b) =>
         Some((a, (b, a + b)))
      }
      println(fibs.take(10).toList) 
   }
}

Save the above program in Demo.scala. Use the following commands to compile and execute this program.

Command

> scalac Demo.scala
> scala Demo

Output

List(0, 1, 1, 2, 3, 5, 8, 13, 21, 34)

In the example, the unfoldStream function generates an infinite stream of Fibonacci numbers by repeatedly. It applies the provided function to compute the next value and state.

Unfold with Trees

You can also use unfold to generate tree-like structures, where each node is generated step by step.

Syntax

The syntax of unfold with trees is -

case class Tree[A](value: A, children: List[Tree[A]])

def unfoldTree[S, A](init: S)(f: S => Option[(A, List[S])]): Tree[A] = f(init) match {
   case Some((a, ss)) => Tree(a, ss.map(unfoldTree(_)(f)))
   case None => throw new Exception("Unfold terminated unexpectedly")
}

Example

Consider the example of generating an infinite binary tree using unfold in Scala programming -

case class Tree[A](value: A, left: () => Tree[A], right: () => Tree[A])

object Demo {
   def unfoldTree[S, A](init: S)(f: S => Option[(A, (S, S))]): Tree[A] = f(init) match {
      case Some((a, (l, r))) =>
      lazy val leftTree = unfoldTree(l)(f)
      lazy val rightTree = unfoldTree(r)(f)
      Tree(a, () => leftTree, () => rightTree)
     case None => throw new Exception("Unfold terminated unexpectedly")
   }

   def main(args: Array[String]): Unit = {
      val tree = unfoldTree(1) { n =>
         Some((n, (n * 2, n * 2 + 1)))
      }
      println(tree.left().left().value)  
      println(tree.right().right().value) 
   }
}

Save the above program in Demo.scala. Use the following commands to compile and execute this program.

Command

> scalac Demo.scala
> scala Demo

Output

4
7

In the example, the unfoldTree function generates an infinite binary tree.

Unfold with Custom Collections

You can define your own collection types and use unfold to generate them.

Syntax

The syntax of unfold with custom collections is -

class CustomCollection[A](elements: List[A]) {
   def unfold[S](init: S)(f: S => Option[(A, S)]): CustomCollection[A] = {
      def loop(s: S): List[A] = f(s) match {
         case Some((a, nextS)) => a :: loop(nextS)
         case None => Nil
      }
      new CustomCollection(loop(init))
   }
}

Example

Consider the example of generating a custom collection of even numbers using unfold in Scala programming -

class CustomCollection[A](val elements: List[A]) {
   def unfold[S](init: S)(f: S => Option[(A, S)]): CustomCollection[A] = {
      def loop(s: S): List[A] = f(s) match {
         case Some((a, nextS)) => a :: loop(nextS)
         case None => Nil
      }
      new CustomCollection(loop(init))
   }

   override def toString: String = elements.toString()
}

object Demo {
   def main(args: Array[String]): Unit = {
      val collection = new CustomCollection[Int](Nil).unfold(0) { n =>
         if (n <= 20) Some((n, n + 2))
         else None
      }
      println(collection) 
   }
}

Save the above program in Demo.scala. Use the following commands to compile and execute this program.

Command

> scalac Demo.scala
> scala Demo

Output

CustomCollection(List(0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20))

In the example, the unfold function generates a custom collection of even numbers up to 20.

Unfold Summary

  • Unfold is a technique for generating collections from a seed value. It repeatedly applies a function that produces the next state and value. These provide flexibility and modularity
  • You can unfold to generate infinite sequences, streams, trees, and custom collections.
  • You can generate complex data structures in a clean and efficient manner in Scala programming.
Advertisements