===== L04. Lists and their higher-order functions ====== /* how to construct lists */ val l0 = List(1,2,3) // the list [1,2,3] has type List[Int] List("apples","banannas") // List[String] List('c','h','a','r') // List[Char] List(Nil, List(3,4), List(5,6)) // List[List[Int]] /* * Lists are: homogeneous - THEY CONTAIN ELEMENTS OF THE * same type * */ /* how to construct lists (using cons, and Nil)*/ (1 :: (2 :: (3 :: Nil))).head //just the same as List(1,2,3) /* e :: l ^ current element ^ the rest of the list */ /* The datatype pair * two values ("first") and ("second"), * not necessarily of the same type * */ val pair = (1, true) pair._1 pair._2 /* Observers for lists */ l0.head // a "function" with zero parameters l0.tail (1,2) :: (3,4) :: Nil //List[(Int,Int)] val l3 = (1,true) :: (2,false) :: Nil // List[(Int,Boolean)] l3.head._2 /* implement stuff with lists */ def contains0 (e: Int, l: List[Int]): Boolean = if (l.isEmpty) false else if (e == l.head) true else contains0(e, l.tail) /* there is a better way to decompose lists: * Pattern matching */ def contains1 (e: Int, l: List[Int]): Boolean = l match { case Nil => false case x :: xs => if (e == x) true else contains1(e,xs) } /* match { case => case => ... case => } What patterns can be: - "ways" to construct objects using THEIR CONSTRUCTORS Examples of patterns: x :: Nil - the list with only one element List(x) - the very same thing x :: 1 :: xs - a list of at least two elements Nil :: xs - a list of lists, where the first element is Nil (1 :: Nil) :: xs - a list of lists where the fst element is [1] (a,b) :: xs - a list of pairs, where the first pair is (a,b) (Nil, 1 :: xs) - a pair where the first elem is the empty list, and the snd is a non-empty list where the fst elem is 1 */ // add 1 to each element of the list def addOne(l: List[Int]): List[Int] = l match { case Nil => Nil case x :: xs => (x + 1) :: addOne(xs) } def fmap (f: Int => Int)(l: List[Int]): List[Int] = l match { case Nil => Nil case x :: xs => f(x) :: fmap(f)(xs) } def multiplyBy(v: Int, l: List[Int]): List[Int] = fmap(_ * v)(l) val l3 = List(List(1,2), List(3,4), List(5,6)) val l4 = List(1,2,4) //map from Scala l3.map(_.isEmpty) l3.map(_.head) /* What is missing from our fmap? */ /* Discussion: a short-hand for writing lambda functions (anonymous functions) x => x * 2 _ * 2 (x, y) => x + y equiv _ + _ (x, y) => x + y * x cannot be written shorter */ def sum(l: List[Int]): Int = l match { case Nil => 0 case x :: xs => x + sum(xs) } def product(l: List[Int]): Int = l match { case Nil => 1 case x :: xs => x * product(xs) } /* Such code is repetitive. What is different: the initial value the operation */ def fold(acc: Int)(op: (Int, Int) => Int)(l: List[Int]) : Int = l match { case Nil => acc case x :: xs => op(x,fold(acc)(op)(xs)) } val l5 = List(1,2,3,4) fold(0)(_ + _)(l5) fold(1)(_ * _)(l5) /* The fold from scala: This fold is called - foldRight */ l5.foldRight(0)(_ + _) /* Why are folds important */ def max(l: List[Int]): Int = { l.tail.foldRight(l.head)((x,crt_max) => if (x > crt_max) x else crt_max) } max(List(1,2,7,3,5,2,9)) def contains(e: Int, l: List[Int]): Boolean = l.foldRight(false)(_ == e || _) contains(12,List(3,4,6,5,6,1,2)) def avg(l: List[Int]): Int = { val p = l.foldRight((0, 0))((x, pair) => (pair._1 + x, pair._2 + 1)) p._1 / p._2 } // def example(f: List[Int] => List[Int], l: List[Int]): Boolean = { ??? } example((x)=>x.map(_ * 2), List(1,2,3)) example(fmap(_ * 2), List(1,2,3))