Objectives:

  • get familiar with pattern matching lists, as well as common list operations from Scala and how they work
  • get familiar with common higher-order functions over lists (partition, map, foldRight, foldLeft, filter)
  • learn about data types in Scala
  • Use the knowledge in real world scenarios such as using sockets or iterating through the filesystem

I. Common list operations

1.1.1. Write a function which returns true if a list of integers has at least k elements. Use patterns.

def atLeastk(k: Int, l: List[Int]): Boolean = {
  if (k == 0) ???
  else ???
}

1.1.2. Write a function which returns the first n elements from a given list. The function should not be implemented as tail-recursive.

def take(n: Int, l: List[Int]): List[Int] = ???
//take(3,List(1,2,3,4,5)) = List(1,2,3)

1.1.3. Write a function which takes a predicate p: Int ⇒ Boolean, a list l and returns a sublist of l containing those elements for which p is true. The function should be curried.

def takeP(p: Int => Boolean)(l: List[Int]): List[Int] = ???
//takeP(_%2 == 0)(List(1,2,3,4,5,6)) = List(2,4,6)

1.1.4. Write a function which uses a predicate to partition (split) a list.

def part(p: Int => Boolean)(l: List[Int]): (List[Int], List[Int]) = ???
// part(_%2 == 0)(List(1,2,3,4,5,6)) = (List(2,4,6),List(1,3,5))

1.2. Gradebooks

More general implementation of taken, dropn and part are already implemented in Scala and can be used as member functions of lists. Examples are shown below:

val l = List(1,2,3,4,5,6,7,8,9)
l.take(3)
l.drop(3)
l.partition(_%2 == 0)

In what follows, we shall encode a gradebook as a list of pairs (<name>,<grade>), where <name> is a String and <grade> is an Int. Example:

val gradebook = List(("G",3), ("F", 10), ("M",6), ("P",4))

To make the type signatures more legible, we can introduce type aliases in Scala:

type Gradebook = List[(String,Int)] //the type Gradebook now refers to a list of pairs of String and Int

Add this type alias to your code before solving the following exercises.

1.2.1. Find the average grade from a gradebook. You must use foldRight.

def average(g: Gradebook): Double = ???

1.2.2. Write a function which takes a gradebook and returns the percentage of failed vs. passed students, as a pair (x,y).

def percentage(g: Gradebook): (Double,Double) = ???

1.2.3. Write a function which takes a gradebook and returns the list of names which have passed. Use filter and map from Scala.

def pass(g: Gradebook): List[String] = ???

1.2.4. Implement a sorting algorithm such as merge sort (in ascending order) over gradebooks:

def mergeSort(l: Gradebook): Gradebook = {
   def merge(u: Gradebook, v: Gradebook): Gradebook = ???
   ???
}

1.2.5 Using assertions check that our merge sort implementation returns the same lists as the stor from Scala.

2.1. Nats

Consider the following toy implementation of the type Nat which encodes natural numbers.

trait Nat {}
case object Zero extends Nat {}
case class Succ(n: Nat) extends Nat {}

For instance, 3 will be encoded as the value: Succ(Succ(Succ(Zero))).

2.1.1. Write a function which implements addition over Nats:

def add(n: Nat, m: Nat): Nat = ???

2.1.2. Write a function which converts a Nat to an Int:

def toInt(n: Nat): Int = ???

2.1.3. Write a function which converts an Int to a Nat.

def fromInt(i: Int): Nat

2.2. Binary Search Trees

In a binary search tree (BST), the key of the current node, is always:

  • smaller or equal than all keys in the right sub-tree.
  • larger or equal than all keys in the left sub-tree.

Consider a binary search tree with keys as integers, encoded as follows:

trait ITree {}
case object Empty extends ITree 
case class INode(key: Int, left: ITree, right: ITree) extends ITree 

2.2.1. Create the tree shown below:

val tree = ???
/*
        5
      /   \
     2     7
    / \     \ 
   1  3      9 
*/

2.2.2. Implement the method size which determines the number of non-empty nodes from the BST.

2.2.3. Define the method contains, which checks if a given integer is a member of the BST.

2.2.4. Implement the method ins which inserts a new integer in the BST. Note: the insertion must return a new BST (the binary search tree property mentioned above must hold after insertion).

2.2.5. Implement a method depth which returns the maximal depth of a BST. Hint: use the method: _.max(_).

(!) 2.2.6. Implement a method minimum which returns the smallest integer from a BST. (If the tree is empty, we return -1). Hint: use the example above, to guide your implementation.

(!) 5.2.7. Implement a method successor(k) which returns the smallest integer from the BST, which is larger than k. Use the following examples for your implementation:

    5             t.successor(2) = 5                      
   / \            t.successor(5) = 6
  2   7           t.successor(7) = 8
     / \          
    6   8

III. Scala in practice

3.1 Write a function reads the contents of a directory and returns True if a given file is in the folder.

3.2 Look into this example and implement a client and echo server using sockets (we are using the Java classes).

3.3 In the client, send a list to the server. The server will return the maximum number from that list.