/**
 * Exercise 3.1 Suppose you are implementing a relational employee database,
 * where the database is a list of tuples name * phone * salary:
 *
 * val db = ("John", "x3456", 50.1) :: ("Jane", "x1234", 107.3) ::
            ("Joan", "unlisted", 12.7) :: Nil
 *
 * 1. Write a function find_salary : string -> float that returns the salary
 * of an employee, given the name.
 *
 * 2. Write a general function
 *
 * select : (string * string * float -> bool) -> (string * string * float) list
 *
 * that returns a list of all the tuples that match the predicate. For example
 * the expression select (fun (_, _, salary) -> salary < 100.0) would return
 * the tuples for John and Joan.
 *
 */

val db = ("John", "x3456", 50.1) :: ("Jane", "x1234", 107.3) ::
         ("Joan", "unlisted", 12.7) :: Nil

// to sintetise the code
type listOfTuples = List[(String, String, Double)]

// 1 Write a function find_salary : string -> float that returns the salary
// of an employee, given the name.
def find_salary(name: String) = {
  def search(t: listOfTuples): Double = t match {
    case (name_, _, salary) :: t if name == name_ => salary
    case _ :: t => search(t)
    case Nil    =>
      throw new Exception("Invalid Argument in find_salary")
  }
  search(db)
}


// Write a general function
// select : (string * string * float -> bool) -> (string * string * float) list
// that returns a list of all the tuples that match the predicate. For example
// the expression select (fun (_, _, salary) -> salary < 100.0) would return
// the tuples for John and Joan.
def select(pred: (String, String, Double) => Boolean) = {
  def search(found: listOfTuples): listOfTuples = found match {
    case (p1, p2, p3) :: t if pred(p1, p2, p3)  => (p1, p2, p3) :: search(t)
    case (p1, p2, p3) :: t if !pred(p1, p2, p3) => search(t)
    case Nil => Nil
    case _ => throw new Exception("Invalid Argument in select function")
  }
  search(db)
}


// tests
println("Searching the salary of 'Joan' at db: " + find_salary("Joan"))
println("")

val predicate = (_:String, _:String, salary:Double) => (salary < 100.0)
println("All employees that match with predicate 'salary < 100.0': ")
println("\t" + select(predicate) + "\n")

 

 

/**
 * Exercise 3.2 The function append : ’a list -> ’a list -> ’a list appends two
 * lists. It can be defined as follows:
 *
 * def append(l1: List[A], l2: List[A]): List[A] = l1 match {
 *   case h :: t => h :: append(t, l2)
 *   case Nil    => l2
 * }
 *
 * Write a tail-recursive version of append.
*/
def append(l1: List[_], l2: List[_]): List[_] = l1 match {
  case h :: t => h :: append(t, l2)
  case Nil    => l2
}

def appendTR(l1: List[_], l2: List[_]) = {
  def iter(l: List[_], ll: List[_]): List[_] = ll match {
    case h :: ll => iter(h :: l, ll)
    case Nil     => l
  }
  iter(l2, l1.reverse)
}

// tests
val l1 = 1 :: 2 :: 3 :: 4 :: Nil
val l2 = 6 :: 7 :: 8 :: Nil

println("Append of " + l1 + " + " + l2 + " = " + append(l1, l2))
println("Tail Recursive of " + l1 + " + " + l2 + " = " + appendTR(l1, l2))

 

 

/**
 * Exercise 3.3. It is known that a welfare crook lives in Los Angeles.
 * You are given lists for
 *
 * 1) people receiving welfare,
 * 2) Hollywood actors, and
 * 3) residents of Beverly Hills.
 *
 * The names in each list are sorted alphabetically (by < ). A welfare crook is
 * someone who appears in all three lists. Write an algorithm to find at least
 * one crook.
 */

def findCrook(people: List[String], actors: List[String],
              residents: List[String]): List[String] =
  (people, actors, residents) match {
    ((ph :: pt), (ah :: at), (rh :: rt)) =>
      if (ph < ah || ph < rh) findCrook(pt, ah, rh)
      else if (ah < ph || ah < rh) findCrook(ph, at, rh)
      else if (rh < ph || rh < ah) findCrook(ph, ag, rt)
      else ph
    _ => throw new Exception("Invalid Argument in findCrook")
  }