Lab 5. Data types in Scala

Objectives:

  • get familiar with algebraic data types
  • get familiar with pattern matching and recursion with them

5.1 Natural Numbers

Given the following implementation of the natural numbers, solve the next few exercises.

trait Nat
case object Zero extends Nat
case class Succ(x: Nat) extends Nat

5.1.1 Write a function which takes two natural numbers, and returns their sum.

def add(x: Nat, y: Nat): Nat = ???

5.1.2 Write a function which takes two natural numbers, and returns their product.

def multiply(x: Nat, y: Nat): Nat = ???

5.1.3 Write a function which takes an int and converts it to a Nat.

def toNat(x: Int): Nat = ???

5.2 Option

Option = carrier (like a box or a container) for a single or no element, of a given type. (Ex. Some(_) or None)

We use Option to write robust functions, in case they return null or fail to return an accepted value.

5.2.1 Let's revisit the function realtrycatch now that we have a type that represents the possibility of error. If an error occurs (try function returns None), the catch function will be called instead.

def realrealtrycatch(t: => Option[Int], c: => Int): Int = {
  ???
}

(!) 5.2.2 Refactor the function toNat(), so that it takes an integer (a positive or negative number) and returns a “container” of a Nat.

def toNatOpt(x: Int): Option[Nat] = ???

(!) 5.2.3 Refactor the function add(), so that it takes two “containers” of Nats and returns a “container” of a Nat.

def addOpt(x: Option[Nat], y: Option[Nat]): Option[Nat] = ???

5.3 Binary Trees

Given the implementation of binary trees from the previous lab:

trait BTree
 
case object TVoid extends BTree
 
case class Node(left: BTree, info: Int, right: BTree) extends BTree
 
def leaf_node(value : Int) : BTree = {
  Node(TVoid, value, TVoid)
}
 
object BTreePrinter {
  case class PrintInfo(len: Int, center: Int, text: List[String])
 
  def pp(tree: BTree): PrintInfo = tree match {
    case TVoid => PrintInfo(3, 2, List("Nil"))
 
    case Node(left, value, right) =>
      val strValue = value.toString
      val ppL = pp(left)
      val ppR = pp(right)
      val nlen = ppL.len + ppR.len + 1
      val ncenter = ppL.len + 1
 
      require(strValue.length <= nlen, "Nice try")
 
      val alignedX = " " * (ncenter - (strValue.length / 2) - 1) + strValue
      val centerLine = " " * (ncenter - 1) + "|"
      val dottedLine = " " * (ppL.center - 1) + "-" * (nlen - ppL.center - (ppR.len - ppR.center + 1) + 2)
      val downLines = " " * (ppL.center - 1) + "|" + " " * (nlen - ppL.center - (ppR.len - ppR.center +1)) + "|"
 
      val combinedLines = zipPad("",(l, r) => l ++ (" " * (ppL.len - l.size + 1 )) ++ r, ppL.text, ppR.text)
 
      PrintInfo(nlen, ncenter, alignedX :: centerLine :: dottedLine :: downLines :: combinedLines)
  }
  def zipPad[A](pad: A, f: (A, A) => A, left: List[A], right: List[A]): List[A] = (left, right) match {
    case (Nil, Nil) => Nil
    case (x :: xs, Nil) => x :: zipPad(pad, f, xs, List(pad))
    case (Nil, y :: ys) => f(pad, y) :: zipPad(pad, f, List(pad), ys)
    case (x :: xs, y :: ys) => f(x, y) :: zipPad(pad, f, xs, ys)
  }
 
  def printTree(tree: BTree): String = "\n" + pp(tree).text.mkString("\n")
}
 
extension(t: BTree) {
  def toStringTree: String = BTreePrinter.printTree(t)
}

And arborică:

val arborica = Node(Node(leaf_node(-1), 5, TVoid), 1, Node(leaf_node(4), 2, Node(leaf_node(3), 6, leaf_node(7))))
arborica.toStringTree

Solve the next few exercises:

5.3.1 Write a function which takes a BinaryTree and returns its depth.

def depth(tree: BTree): Int = ???

5.3.2 Write a function which takes a BinaryTree and returns the number of nodes in its subtree.

def subtree(tree: BTree): Int = ???

5.3.3 Write a function which takes a BinaryTree and returns the number of nodes with an even number of children.

def evenChildCount(tree: BTree): Int = ???

5.3.4 Write a function which takes a BinaryTree and returns the number of nodes whose values follow a certain rule.

def countNodes(tree: BTree, cond: Int => Boolean): Int = ???

(!) 5.3.5 Write a function which takes two BinaryTree and tries to assign the second tree as a child of the first. It should return a “container” of a BinaryTree .

def append(tree1: BTree, tree2: BTree): Option[BTree] = ???

5.4 Expression evaluation

Given the following implementation of expressions, solve the next few exercises.

trait Expr
case class Atom(a: Int) extends Expr
case class Add(e1: Expr, e2: Expr) extends Expr
case class Mult(e1: Expr, e2: Expr) extends Expr

5.4.1 Write a function which takes an Expression and evaluates it.

def evaluate(e: Expr): Int = ???

5.4.2 Write a function which takes an Expression and simplifies it. (Ex. a * (b + c) → remove parentheses → ab + ac)

def simplify(e: Expr): Expr = ???

5.4.3 Write a function which takes an Expression and removes 'useless' operations. (Ex. a * 1 → a, a + 0 → a)

def optimize(e: Expr): Expr = ???

Click to display ⇲

Click to hide ⇱

If you work outside of worksheets, you can define the trait as:

trait Expr {
    def + (that: Expr): Expr = Add(this, that)
    def * (that: Expr): Expr = Mul(this, that)
}
case class Atom(a: Int) extends Expr
case class Add(e1: Expr, e2: Expr) extends Expr
case class Mult(e1: Expr, e2: Expr) extends Expr

With the operators defined, you can create expressions writting:

(Atom(1) + Atom(2) * Atom(3)) + Atom(4)

instead of:

Add(Add(Atom(1), Mult(Atom(2), Atom(3))), Atom(4))

5.5 Matrix manipulation

We shall represent matrices as lists of lists, i.e. values of type [ [Integer ] ]. Each element in the outer list represents a line of the matrix. Hence, the matrix

$ \displaystyle \left(\begin{array}{ccc} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \\ \end{array}\right)$

will be represented by the list [ [1,2,3],[4,5,6],[7,8,9] ].

To make signatures more legible, add the type alias to your code:

 type Matrix = List[List[Int]] 

which makes the type-name Matrix stand for [ [Integer] ].

5.5.1 Write a function that computes the scalar product with an integer:

$ \displaystyle 2 * \left(\begin{array}{ccc} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \\ \end{array}\right) = \left(\begin{array}{ccc} 2 & 4 & 6 \\ 8 & 10 & 12 \\ 14 & 16 & 18 \\ \end{array}\right)$

def scalarProd(m: Matrix)(v: Int): Matrix = ??? 

5.5.2 Write a function which adjoins two matrices by extending rows (horizontally):

$ \displaystyle \left(\begin{array}{cc} 1 & 2 \\ 3 & 4\\\end{array}\right) hjoin \left(\begin{array}{cc} 5 & 6 \\ 7 & 8\\\end{array}\right) = \left(\begin{array}{cc} 1 & 2 & 5 & 6 \\ 3 & 4 & 7 & 8\\\end{array}\right) $

def hJoin(m1: Matrix, m2: Matrix): Matrix = ???

5.5.3 Write a function which adjoins two matrices by adding new rows (vertically):

$ \displaystyle \left(\begin{array}{cc} 1 & 2 \\ 3 & 4\\\end{array}\right) vjoin \left(\begin{array}{cc} 5 & 6 \\ 7 & 8\\\end{array}\right) = \left(\begin{array}{cc} 1 & 2 \\ 3 & 4 \\ 5 & 6\\ 7 & 8\\ \end{array}\right) $

def vJoin(m1: Matrix, m2: Matrix): Matrix = ???

5.5.4 Write a function which adds two matrices, element by element:

def matSum(m1: Matrix, m2: Matrix): Matrix = ???

5.5.5 Write a function that transposed a matrix:

$ \left(\begin{array}{ccc} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \\ \end{array}\right)^{T} = \left(\begin{array}{ccc} 1 & 4 & 7 \\ 2 & 5 & 8 \\ 3 & 6 & 9 \\ \end{array}\right)$

def transposed(m: Matrix): Matrix = ??? 

5.5.6 Write a function that computes the matrix product:

$ \left(\begin{array}{ccc} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \\ \end{array}\right) * \left(\begin{array}{ccc} 1 & 2 \\ 3 & 4 \\ 5 & 6 \\ \end{array}\right) = \left(\begin{array}{ccc} 22 & 28 \\ 49 & 64 \\ 76 & 100 \\ \end{array}\right)$

(For an element of the result $ a_{ij}$ the value is obtained by selecting the i-th row and the j-th column, computing the products of the elements at the same index and adding all those results up:

E. g. 22 = 1 * 1 + 2 * 3 + 3 * 5)

def matrixProd(m1: Matrix, m2: Matrix): Matrix = ???