Edit this page Backlinks This page is read only. You can view the source, but not change it. Ask your administrator if you think this is wrong. ====== 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. <code scala> sealed trait Nat case object Zero extends Nat case class Succ(x: Nat) extends Nat </code> **5.1.1** Write a function which takes two natural numbers, and returns their sum. <code scala> def add(x: Nat, y: Nat): Nat = ??? </code> **5.1.2** Write a function which takes two natural numbers, and returns their product. <code scala> def multiply(x: Nat, y: Nat): Nat = ??? </code> **5.1.3** Write a function which takes an Int and converts it to a Nat. Write the inverse function, which takes a Nat and converts it to Int. <code scala> def toNat(x: Int): Nat = ??? def toInt(x: Nat): Int = ??? </code> <hidden Automatic conversions> <code scala> given nat2Int: Conversion[Nat, Int] with def apply(x: Nat): Int = toInt(x) given int2Nat: Conversion[Int, Nat] with def apply(x: Int): Nat = toNat(x) // the second conversion is used to cast params from Int to Nat // type Nat, by default val defaultTypeResult = add(5, 7) // type Int, by type annotation and the first conversion val intResult: Int = add(5, 7) </code> </hidden> ==== 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. <code scala> def realrealtrycatch(t: => Option[Int], c: => Int): Int = { ??? } </code> **(!) 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. <code scala> def toNatOpt(x: Int): Option[Nat] = ??? </code> **(!) 5.2.3** Refactor the function add(), so that it takes two "containers" of Nats and returns a "container" of a Nat. <code scala> def addOpt(x: Option[Nat], y: Option[Nat]): Option[Nat] = ??? </code> ==== 5.3 Binary Trees ==== Given the implementation of binary trees from the previous lab: <code scala> sealed trait BinaryTree { override def toString: String = super.toString: String } case object TVoid extends BinaryTree { override def toString: String = "-" } case class Node(left: BinaryTree, info: Int, right: BinaryTree) extends BinaryTree { override def toString: String = { def printTree( tree: BinaryTree, prefix: String = "", isLeft: Boolean = true ): String = tree match { case TVoid => "" case Node(l, value, r) => val rightStr = printTree(r, prefix + (if (isLeft && prefix.nonEmpty) "│ " else " "), isLeft = false) val nodeStr = prefix + (if (tree != this) if (isLeft) "└── " else "┌── " else " ") + value.toString + "\n" val leftStr = printTree(l, prefix + (if (isLeft) " " else "│ ")) rightStr + nodeStr + leftStr } "\n" + printTree(this) } } def leaf_node(value : Int) : BinaryTree = Node(TVoid, value, TVoid) </code> Please copy-paste this definition in your ''Main.scala'' file from your laboratory project, in order for printTree to work properly. Call print from the function main and click the "run" text above the definition of main to see the result in terminal. And arborică: <code scala> val arborica = Node(Node(leaf_node(-1), 5, TVoid), 1, Node(leaf_node(4), 2, Node(leaf_node(3), 6, leaf_node(7)))) </code> Solve the next few exercises: **5.3.1** Write a function which takes a BinaryTree and returns its depth. <code scala> def depth(tree: BinaryTree): Int = ??? </code> **5.3.2** Write a function which takes a BinaryTree and returns the number of nodes in its subtree. <code scala> def subtree(tree: BinaryTree): Int = ??? </code> **5.3.3** Write a function which takes a BinaryTree and returns the number of nodes with an even number of children. <code scala> def evenChildCount(tree: BinaryTree): Int = ??? </code> **5.3.4** Write a function which takes a BinaryTree and returns the number of nodes whose values follow a certain rule. <code scala> def countNodes(tree: BinaryTree, cond: Int => Boolean): Int = ??? </code> **(!) 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 . <code scala> def append(tree1: BinaryTree, tree2: BinaryTree): Option[BinaryTree] = ??? </code> ==== 5.4 Expression evaluation ==== Given the following implementation of expressions, solve the next few exercises. <code scala> 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 </code> **5.4.1** Write a function which takes an Expression and evaluates it. <code scala> def evaluate(e: Expr): Int = ??? </code> **5.4.2** Write a function which takes an Expression and simplifies it. (Ex. a * (b + c) -> remove parentheses -> ab + ac) <code scala> def simplify(e: Expr): Expr = ??? </code> **5.4.3** Write a function which takes an Expression and removes 'useless' operations. (Ex. a * 1 -> a, a + 0 -> a) <code scala> def optimize(e: Expr): Expr = ??? </code> <hidden> If you work outside of worksheets, you can define the trait as: <code scala> sealed 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 </code> With the operators defined, you can create expressions writting: <code scala> (Atom(1) + Atom(2) * Atom(3)) + Atom(4) </code> instead of: <code scala> Add(Add(Atom(1), Mult(Atom(2), Atom(3))), Atom(4)) </code> </hidden> ==== 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 $math[ \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: <code scala> type Matrix = List[List[Int]] </code> which makes the type-name ''Matrix'' stand for ''[ [Integer] ]''. **5.5.1** Write a function that computes the scalar product with an integer: $math[ \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)] <code scala> def scalarProd(m: Matrix)(v: Int): Matrix = ??? </code> **5.5.2** Write a function which adjoins two matrices by extending rows (horizontally): $math[ \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) ] <code scala> def hJoin(m1: Matrix, m2: Matrix): Matrix = ??? </code> **5.5.3** Write a function which adjoins two matrices by adding new rows (vertically): $math[ \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) ] <code scala> def vJoin(m1: Matrix, m2: Matrix): Matrix = ??? </code> **5.5.4** Write a function which adds two matrices, element by element: <code scala> def matSum(m1: Matrix, m2: Matrix): Matrix = ??? </code> **5.5.5** Write a function that transposed a matrix: $math[ \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)] <code scala> def transposed(m: Matrix): Matrix = ??? </code> **5.5.6** Write a function that computes the matrix product: $math[ \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 $math[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) <code scala> def matrixProd(m1: Matrix, m2: Matrix): Matrix = ??? </code>