Get, Member, Add, Remove

List Recursion

Instead of using the non-functional for loop (loop control variable), we can process lists recursively: if the list is empty, then we're done, otherwise we do something with the head of the list, and then recursively process the tail:

// fetch member at a given position
def get[T](pos: Int, vals: List[T]): T = {
   if (vals == Nil) throw new Exception("List is too short");
   if (pos < 0) throw new Exception("position must not be negative");
   if (pos == 0) vals.head else get(pos - 1, vals.tail)
}

// search for an element
def member[T](elem: T, vals: List[T]): Boolean = {
   if (vals == Nil) false
   else if (elem == vals.head) true
   else member(elem, vals.tail)
}

// add an element at a given position
def add[T](elem: T, pos: Int, vals: List[T]): List[T] = {
   if (vals == Nil && pos > 0) throw new Exception("List is too short");
   if (pos < 0) throw new Exception("position must not be negative");
   if (pos == 0) elem:: vals
   else vals.head::add(elem, pos - 1, vals.tail)
}

// remove element t a given position
def rem[T](pos: Int, vals: List[T]): List[T] = {
   if (vals == Nil) throw new Exception("List is too short");
   if (pos < 0) throw new Exception("position must not be negative");
   if (pos == 0) vals.tail
   else vals.head::rem(pos - 1, vals.tail)
}

Notes:

·       These are all good examples of generic functions

·       All of these functions are non-destructive. For example:

scala> val ages = List(21, 13, 12, 44, 18, 19)
ages: List[Int] = List(21, 13, 12, 44, 18, 19)

scala> rem(2, ages)
res8: List[Int] = List(21, 13, 44, 18, 19) // 12 missing in result

scala> ages
res9: List[Int] = List(21, 13, 12, 44, 18, 19) // 12 still there in ages

·       How would you implement these functions using iteration (for loops)

Tail Recursion

Processing a length n list usually requires O(n) time for both recursion and iteration. However, iteration only requires O(1) space, while recursion makes n recursive calls that gobble up O(n) space. We can remedy this situation in languages that are optimized to use tail recursion. In a tail recursive function no additional work is pending after the recursive call. The member function above is tail recursive, while the rem function is not. (Why?)

Example: Summing a list of numbers

Each of the following three functions correctly returns the sum of a list of numbers in O(n) time, where n = the length of the list:

def iterSum(nums: List[Double]): Double = {
   var result = 0.0
   for(i <- nums) result += i
   result
}

def recursiveSum(nums: List[Double]): Double = {
   if (nums == Nil) 0 else nums.head + recursiveSum(nums.tail)
}

def tailRecursiveSum(nums: List[Double]): Double = {
   def helper(result: Double, unseen: List[Double]): Double = {
      if (unseen == Nil) result else helper(result + unseen.head, unseen.tail)
   }
   helper(0.0, nums)
}

However, iterSum and tailRecursiveSum consume O(1) memory, while recursiveSum consumes O(n) memory. To see this, trace the computations of all three given a list of three numbers.

Notes

·       Strictly speaking, tailRecursiveSum isn't recursive at all. But its internal helper function is tail recursive.

·       Adding an internal helper function that passes a partial result along with the unseen tail of a list to its recursive call is a typical way of making tail recursive functions.

·       Every recursive function can be turned into a tail recursive function, making non-functional iterations unnecessary.

·       How would you reimplement add and rem as tail recursions? (Hard.)