progs/lecture2.scala
changeset 192 a112e0e2325c
parent 174 90e0b1cc460b
child 204 9b45dd24271b
--- a/progs/lecture2.scala	Wed Jul 04 14:48:05 2018 +0100
+++ b/progs/lecture2.scala	Fri Oct 05 11:23:15 2018 +0100
@@ -2,102 +2,34 @@
 //=================
 
 
-// the pain with overloaded math operations
-
-(100 / 4)
-
-(100 / 3)
-
-(100.toDouble / 3.toDouble)
-
-
-// For-Comprehensions again
-//==========================
-
-def square(n: Int) : Int = n * n
-
-for (n <- (1 to 10).toList) yield {
-  val res = square(n)
-  res
-}
-
-// like in functions, the "last" item inside the yield
-// will be returned; the last item is not necessarily 
-// the last line
-
-for (n <- (1 to 10).toList) yield {
-  if (n % 2 == 0) n 
-  else square(n)
-}
-
-
-// ...please, please do not write:
-val lst = List(1, 2, 3, 4, 5, 6, 7, 8, 9)
-
-for (i <- (0 until lst.length).toList) yield square(lst(i))
-
-// this is just so prone to off-by-one errors;
-// write instead
-
-for (e <- lst; if (e % 2) == 0; if (e != 4)) yield square(e)
-
-
-//this works for sets as well
-val st = Set(1, 2, 3, 4, 5, 6, 7, 8, 9)
-
-for (e <- st) yield {
-  if (e < 5) e else square(e)
-}
-
-
-
-// Side-Effects
-//==============
-
-// with only a side-effect (no list is produced),
-// for has no "yield"
-
-for (n <- (1 to 10)) println(n)
-
-
-for (n <- (1 to 10)) {
-  print("The number is: ")
-  print(n)
-  print("\n")
-}
-
-
-
-
-// know when to use yield and when not:
-
-val test = 
- for (e <- Set(1, 2, 3, 4, 5, 6, 7, 8, 9); if e < 5) yield square(e)
-
-
-
 // Option type
 //=============
 
-//in Java, if something unusually happens, you return null;
+//in Java if something unusually happens, you return null;
 //in Scala you use Option
 //   - if the value is present, you use Some(value)
 //   - if no value is present, you use None
 
 
-List(7,24,3,4,5,6).find(_ < 4)
+List(7,2,3,4,5,6).find(_ < 4)
 List(5,6,7,8,9).find(_ < 4)
 
-List(7,2,3,4,5,6).filter(_ < 4)
 
-// some operations on Option's
+// Values in types
+//
+// Boolean: 
+// Int: 
+// String: 
+//
+// Option[String]:
+//   
+
 
 val lst = List(None, Some(1), Some(2), None, Some(3))
 
 lst.flatten
 
-Some(10).get
-None.get
+Some(1).get
 
 Some(1).isDefined
 None.isDefined
@@ -108,55 +40,38 @@
   if (y == 0) None else Some(x / y)
 }
 
-// use .getOrElse is for setting a default value
+// getOrElse is for setting a default value
 
 val lst = List(None, Some(1), Some(2), None, Some(3))
-
 for (x <- lst) yield x.getOrElse(0)
 
 
 
 
-// error handling with Options (no exceptions)
-//
-//  Try(....)
+// error handling with Option (no exceptions)
 //
 //  Try(something).getOrElse(what_to_do_in_an_exception)
 //
 import scala.util._
-
-Try(1 + 3)
-Try(9 / 0) 
-
-Try(9 / 3).getOrElse(42) 
-Try(9 / 0).getOrElse(42) 
-
-
 import io.Source
 
-val my_url = """https://nms.kcl.ac.uk/christian.urban"""
-//val my_url = """https://nms.kcl.ac.uk/christan.urban"""  // misspelled
-
-Source.fromURL(my_url).mkString
+Source.fromURL("""http://www.inf.kcl.ac.uk/staff/urbanc/""").mkString
 
-Try(Source.fromURL(my_url).mkString).getOrElse("")
+Try(Source.fromURL("""http://www.inf.kcl.ac.uk/staff/urbanc/""").mkString).getOrElse("")
 
-Try(Some(Source.fromURL(my_url).mkString)).getOrElse(None)
-
+Try(Some(Source.fromURL("""http://www.inf.kcl.ac.uk/staff/urbanc/""").mkString)).getOrElse(None)
 
 // a function that turns strings into numbers
-Integer.parseInt("1234")
-
+Integer.parseInt("12u34")
 
 def get_me_an_int(s: String): Option[Int] = 
  Try(Some(Integer.parseInt(s))).getOrElse(None)
 
 val lst = List("12345", "foo", "5432", "bar", "x21")
-
 for (x <- lst) yield get_me_an_int(x)
 
 // summing all the numbers
-val sum = (for (i <- lst) yield get_me_an_int(i)).flatten.sum
+val sum = lst.flatMap(get_me_an_int(_)).sum
 
 
 // This may not look any better than working with null in Java, but to
@@ -165,31 +80,215 @@
 // write that function.
 //
 // In Java, if you didn't write this function, you'd have to depend on
-// the Javadoc of get_me_an_int. If you didn't look at the Javadoc, 
+// the Javadoc of the get_me_an_int. If you didn't look at the Javadoc, 
 // you might not know that get_me_an_int could return a null, and your 
 // code could potentially throw a NullPointerException.
 
 
+
 // even Scala is not immune to problems like this:
 
-List(5,6,7,8,9).indexOf(42)
+List(5,6,7,8,9).indexOf(7)
+
+
+
+
+
+// Type abbreviations
+//====================
+
+// some syntactic convenience
+type Pos = (int, Int)
+
+type Board = List[List[Int]]
+
+
+
+// Implicits
+//===========
+//
+// for example adding your own methods to Strings:
+// imagine you want to increment strings, like
+//
+//     "HAL".increment
+//
+// you can avoid ugly fudges, like a MyString, by
+// using implicit conversions
+
+
+implicit class MyString(s: String) {
+  def increment = for (c <- s) yield (c + 1).toChar 
+}
+
+"HAL".increment
 
 
-// ... how are we supposed to know that this returns -1
+// No return in Scala
+//====================
+
+//You should not use "return" in Scala:
+//
+// A return expression, when evaluated, abandons the 
+// current computation and returns to the caller of the 
+// function in which return appears."
+
+def sq1(x: Int): Int = x * x
+def sq2(x: Int): Int = return x * x
+
+def sumq(ls: List[Int]): Int = {
+  (for (x <- ls) yield (return x * x)).sum[Int]
+}
+
+sumq(List(1,2,3,4))
+
+
+// last expression in a function is the return statement
+def square(x: Int): Int = {
+  println(s"The argument is ${x}.")
+  x * x
+}
+
+
+
+// Pattern Matching
+//==================
+
+// A powerful tool which is supposed to come to Java in a few years
+// time (https://www.youtube.com/watch?v=oGll155-vuQ)...Scala already
+// has it for many years ;o)
+
+// The general schema:
+//
+//    expression match {
+//       case pattern1 => expression1
+//       case pattern2 => expression2
+//       ...
+//       case patternN => expressionN
+//    }
+
+
+
+
+// remember
+val lst = List(None, Some(1), Some(2), None, Some(3)).flatten
+
+
+def my_flatten(xs: List[Option[Int]]): List[Int] = {
+  ...
+}
+
+
+def my_flatten(lst: List[Option[Int]]): List[Int] = lst match {
+  case Nil => Nil
+  case None::xs => my_flatten(xs)
+  case Some(n)::xs => n::my_flatten(xs)
+}
 
 
-//other example for options...NaN
-val squareRoot: PartialFunction[Double, Double] = { 
-    case d: Double if d > 0 => Math.sqrt(d) 
+// another example
+def get_me_a_string(n: Int): String = n match {
+  case 0 => "zero"
+  case 1 => "one"
+  case 2 => "two"
+  case _ => "many"
+}
+
+get_me_a_string(0)
+
+// you can also have cases combined
+def season(month: String) = month match {
+  case "March" | "April" | "May" => "It's spring"
+  case "June" | "July" | "August" => "It's summer"
+  case "September" | "October" | "November" => "It's autumn"
+  case "December" | "January" | "February" => "It's winter"
+}
+ 
+println(season("November"))
+
+// What happens if no case matches?
+
+println(season("foobar"))
+
+// fizz buzz
+def fizz_buzz(n: Int) : String = (n % 3, n % 5) match {
+  case (0, 0) => "fizz buzz"
+  case (0, _) => "fizz"
+  case (_, 0) => "buzz"
+  case _ => n.toString  
+}
+
+for (n <- 0 to 20) 
+ println(fizz_buzz(n))
+
+
+// User-defined Datatypes
+//========================
+
+abstract class Tree
+case class Node(elem: Int, left: Tree, right: Tree) extends Tree
+case class Leaf() extends Tree
+
+
+def insert(tr: Tree, n: Int): Tree = tr match {
+  case Leaf() => Node(n, Leaf(), Leaf())
+  case Node(m, left, right) => 
+    if (n == m) Node(m, left, right) 
+    else if (n < m) Node(m, insert(left, n), right)
+    else Node(m, left, insert(right, n))
 }
 
-val list: List[Double] = List(4, 16, 25, -9)
+
+val t1 = Node(4, Node(2, Leaf(), Leaf()), Node(7, Leaf(), Leaf()))
+insert(t1, 3)
+
+def depth(tr: Tree): Int = tr match {
+  case Leaf() => 0
+  case Node(_, left, right) => 1 + List(depth(left), depth(right)).max
+}
+
+
+def balance(tr: Tree): Int = tr match {
+  case Leaf() => 0
+  case Node(_, left, right) => depth(left) - depth(right)
+}
+
+balance(insert(t1, 3))
+
+// another example
+
+abstract class Person
+case class King() extends Person
+case class Peer(deg: String, terr: String, succ: Int) extends Person
+case class Knight(name: String) extends Person
+case class Peasant(name: String) extends Person
+case class Clown() extends Person
 
-val result = list.map(Math.sqrt)
-// => result: List[Double] = List(2.0, 4.0, 5.0, NaN)
+def title(p: Person): String = p match {
+  case King() => "His Majesty the King"
+  case Peer(deg, terr, _) => s"The ${deg} of ${terr}"
+  case Knight(name) => s"Sir ${name}"
+  case Peasant(name) => name
+}
 
-val result = list.collect(squareRoot)
-// => result: List[Double] = List(2.0, 4.0, 5.0)
+def superior(p1: Person, p2: Person): Boolean = (p1, p2) match {
+  case (King(), _) => true
+  case (Peer(_,_,_), Knight(_)) => true
+  case (Peer(_,_,_), Peasant(_)) => true
+  case (Peer(_,_,_), Clown()) => true
+  case (Knight(_), Peasant(_)) => true
+  case (Knight(_), Clown()) => true
+  case (Clown(), Peasant(_)) => true
+  case _ => false
+}
+
+val people = List(Knight("David"), 
+                  Peer("Duke", "Norfolk", 84), 
+                  Peasant("Christian"), 
+                  King(), 
+                  Clown())
+
+println(people.sortWith(superior(_, _)).mkString(", "))
+
 
 
 // Higher-Order Functions
@@ -199,91 +298,35 @@
 
 val lst = (1 to 10).toList
 
-def even(x: Int) : Boolean = x % 2 == 0
-def odd(x: Int) : Boolean = x % 2 == 1
+def even(x: Int): Boolean = x % 2 == 0
+def odd(x: Int): Boolean = x % 2 == 1
 
-lst.filter(x => even(x) && odd(x))
+lst.filter(x => even(x))
 lst.filter(even(_))
-lst.filter(odd && even)
+lst.filter(even)
 
 lst.find(_ > 8)
 
-// map applies a function to each element of a list
-
 def square(x: Int): Int = x * x
 
-val lst = (1 to 10).toList
 lst.map(square)
 
 lst.map(square).filter(_ > 4)
 
 lst.map(square).filter(_ > 4).map(square)
 
-// map works for most collection types, including sets
-Set(1, 3, 6).map(square).filter(_ > 4)
-
-
-val l = List((1, 3),(2, 4),(4, 1),(6, 2))
-
-l.map(square(_._1))
-
-
-// Why are functions as arguments useful?
-//
-// Consider the sum between a and b:
-
-def sumInts(a: Int, b: Int) : Int = 
-  if (a > b) 0 else a + sumInts(a + 1, b)
-
-
-sumInts(10, 16)
-
-// sum squares
-def square(n: Int) : Int = n * n
-
-def sumSquares(a: Int, b: Int) : Int = 
-  if (a > b) 0 else square(a) + sumSquares(a + 1, b)
-
-sumSquares(2, 6)
+// in my collatz.scala
+//(1 to bnd).map(i => (collatz(i), i)).maxBy(_._1)
 
 
-// sum factorials
-def fact(n: Int) : Int =  
-  if (n == 0) 1 else n * fact(n - 1)
-
-def sumFacts(a: Int, b: Int) : Int = 
-  if (a > b) 0 else fact(a) + sumFacts(a + 1, b)
-
-sumFacts(2, 6)
+// type of functions, for example f: Int => Int
 
-
-
-// You can see the pattern....can we simplify our work?
-// The type of functions from ints to ints: Int => Int
-
-def sum(f: Int => Int, a: Int, b: Int) : Int = {
-  if (a > b) 0 
-  else f(a) + sum(f, a + 1, b)
+def my_map_int(lst: List[Int], f: Int => Int): List[Int] = lst match {
+  case Nil => Nil
+  case x::xs => f(x)::my_map_int(xs, f)
 }
 
-
-def sumSquares(a: Int, b: Int) : Int = sum(square, a, b)
-def sumFacts(a: Int, b: Int) : Int = sum(fact, a, b)
-
-// What should we do for sumInts?
-
-def id(n: Int) : Int = n
-def sumInts(a: Int, b: Int) : Int = sum(id, a, b)
-
-sumInts(10, 12)
-
-
-// Anonymous Functions: You can also write:
-
-def sumCubes(a: Int, b: Int) : Int =   sum(x => x * x * x, a, b)
-def sumSquares(a: Int, b: Int) : Int = sum(x => x * x, a, b)
-def sumInts(a: Int, b: Int) : Int    = sum(x => x, a, b)
-
+my_map_int(lst, square)
 
 // other function types
 //
@@ -292,204 +335,56 @@
 // ... 
 
 
-// an aside: partial application
-
-def add(a: Int)(b: Int) : Int = a + b
-def add_abc(a: Int)(b: Int)(c: Int) : Int = a + b + c
-
-val add2 : Int => Int = add(2)
-add2(5)
-
-val add2_bc : Int => Int => Int = add_abc(2) 
-val add2_9_c : Int => Int = add2_bc(9) 
-
-add2_9_c(10)
-
-sum(add(2), 0, 2)
-sum(add(10), 0, 2)
-
-
-
-
-// some automatic timing in each evaluation
-package wrappers {  
-
-  object wrap { 
-   
-    def timed[R](block: => R): R = {
-      val t0 = System.nanoTime()
-      val result = block
-      println("Elapsed time: " + (System.nanoTime - t0) + "ns")
-      result
-    }
-
-    def apply[A](a: => A): A = { 
-      timed(a)
-    } 
-  }
+def sumOf(f: Int => Int, lst: List[Int]): Int = lst match {
+  case Nil => 0
+  case x::xs => f(x) + sumOf(f, xs)
 }
 
-$intp.setExecutionWrapper("wrappers.wrap")
+def sum_squares(lst: List[Int]) = sumOf(square, lst)
+def sum_cubes(lst: List[Int])   = sumOf(x => x * x * x, lst)
 
-// Iteration
+sum_squares(lst)
+sum_cubes(lst)
+
+// lets try it factorial
+def fact(n: Int): Int = ...
 
-def fib(n: Int) : Int = 
-  if (n <= 1) 1 else fib(n - 1) + fib(n - 2)
+def sum_fact(lst: List[Int]) = sumOf(fact, lst)
+sum_fact(lst)
 
-fib(10)
-
+// Avoid being mutable
+//=====================
 
-Iterator.iterate((1,1)){ case (n: Int, m: Int) => (n + m, n) }.drop(9).next
-
+// a student showed me...
+import scala.collection.mutable.ListBuffer
 
 
 
-// Function Composition
-//======================
-
-// How can be Higher-Order Functions and Options be helpful?
-
-def add_footer(msg: String) : String = msg ++ " - Sent from iOS"
-
-def valid_msg(msg: String) : Boolean = msg.size <= 140
-
-def duplicate(s: String) : String = s ++ s
+def collatz_max(bnd: Long): (Long, Long) = {
+  val colNos = ListBuffer[(Long, Long)]()
+  for (i <- (1L to bnd).toList) colNos += ((collatz(i), i))
+  colNos.max
+}
 
-// they compose very nicely, e.g
-
-valid_msg(add_footer("Hello World"))
-valid_msg(duplicate(duplicate(add_footer("Helloooooooooooooooooo World"))))
-
-// but not all functions do
-// first_word: let's first do it the ugly Java way using null:
-
-def first_word(msg: String) : String = {
-  val words = msg.split(" ")
-  if (words(0) != "") words(0) else null
+def collatz_max(bnd: Long): (Long, Long) = {
+  (1L to bnd).map((i) => (collatz(i), i)).maxBy(_._1)
 }
 
-duplicate(first_word("Hello World"))
-duplicate(first_word(""))
-
-def extended_duplicate(s: String) : String = 
-  if (s != null) s ++ s else null
-
-extended_duplicate(first_word(""))
-
-// but this is against the rules of the game: we do not want
-// to change duplicate, because first_word might return null
-
-
-// Avoid always null!
-def better_first_word(msg: String) : Option[String] = {
-  val words = msg.split(" ")
-  if (words(0) != "") Some(words(0)) else None
+//views -> lazy collection
+def collatz_max(bnd: Long): (Long, Long) = {
+  (1L to bnd).view.map((i) => (collatz(i), i)).maxBy(_._1)
 }
 
-better_first_word("Hello World").map(duplicate)
-
-better_first_word("Hello World").map(duplicate)
-better_first_word("").map(duplicate).map(duplicate).map(valid_msg)
+// raises a GC exception
+(1 to 1000000000).filter(_ % 2 == 0).take(10).toList
+// ==> java.lang.OutOfMemoryError: GC overhead limit exceeded
 
-better_first_word("").map(duplicate)
-better_first_word("").map(duplicate).map(valid_msg)
-
-
+(1 to 1000000000).view.filter(_ % 2 == 0).take(10).toList
 
 
 
-// Problems with mutability and parallel computations
-//====================================================
-
-def count_intersection(A: Set[Int], B: Set[Int]) : Int = {
-  var count = 0
-  for (x <- A; if (B contains x)) count += 1 
-  count
-}
-
-val A = (1 to 1000).toSet
-val B = (1 to 1000 by 4).toSet
-
-count_intersection(A, B)
-
-// but do not try to add .par to the for-loop above,
-// otherwise you will be caught in race-condition hell.
-
-
-//propper parallel version
-def count_intersection2(A: Set[Int], B: Set[Int]) : Int = 
-  A.par.count(x => B contains x)
-
-count_intersection2(A, B)
-
-
-//for measuring time
-def time_needed[T](n: Int, code: => T) = {
-  val start = System.nanoTime()
-  for (i <- (0 to n)) code
-  val end = System.nanoTime()
-  (end - start) / 1.0e9
-}
-
-val A = (1 to 1000000).toSet
-val B = (1 to 1000000 by 4).toSet
-
-time_needed(10, count_intersection(A, B))
-time_needed(10, count_intersection2(A, B))
-
-
-
-
-
-
-// No returns in Scala
-//====================
-
-// You should not use "return" in Scala:
-//
-// A return expression, when evaluated, abandons the 
-// current computation and returns to the caller of the 
-// function in which return appears."
-
-def sq1(x: Int): Int = x * x
-def sumq(ls: List[Int]): Int = 
-  ls.map(x => x * x).sum
-
-
-
-
-def sq2(x: Int): Int = return x * x
-
-def sumq(ls: List[Int]): Int = {
-  ls.map(sq1).sum[Int]
-}
-
-sumq(List(1, 2, 3, 4))
-
-
-
-def sumq(ls: List[Int]): Int = {
-  val sqs : List[Int] = for (x <- ls) yield (return x * x)
-  sqs.sum
-}
-
-sumq(List(1, 2, 3, 4))
-
-
-
-// Type abbreviations
-//====================
-
-// some syntactic convenience
-
-type Pos = (int, Int)
-type Board = List[List[Int]]
-
-
-
-
-// Sudoku in Scala
-//=================
+// Sudoku
+//========
 
 // THE POINT OF THIS CODE IS NOT TO BE SUPER
 // EFFICIENT AND FAST, just explaining exhaustive
@@ -514,19 +409,15 @@
 val indexes = (0 to 8).toList
 
 
-def empty(game: String) = game.indexOf(EmptyValue)
-def isDone(game: String) = empty(game) == -1 
-def emptyPosition(game: String) = 
-  (empty(game) % MaxValue, empty(game) / MaxValue)
 
 
-def get_row(game: String, y: Int) = 
-  indexes.map(col => game(y * MaxValue + col))
-def get_col(game: String, x: Int) = 
-  indexes.map(row => game(x + row * MaxValue))
+def empty(game: String) = game.indexOf(EmptyValue)
+def isDone(game: String) = empty(game) == -1 
+def emptyPosition(game: String) = (empty(game) % MaxValue, empty(game) / MaxValue)
 
-get_row(game0, 3)
-get_col(game0, 0)
+
+def get_row(game: String, y: Int) = indexes.map(col => game(y * MaxValue + col))
+def get_col(game: String, x: Int) = indexes.map(row => game(x + row * MaxValue))
 
 def get_box(game: String, pos: Pos): List[Char] = {
     def base(p: Int): Int = (p / 3) * 3
@@ -536,34 +427,28 @@
     (x0 until x0 + 3).toList.flatMap(x => ys.map(y => game(x + y * MaxValue)))
 }
 
-get_box(game0, (0, 0))
-get_box(game0, (1, 1))
-get_box(game0, (2, 1))
 
-// this is not mutable!!
-def update(game: String, pos: Int, value: Char): String = 
-  game.updated(pos, value)
+//get_row(game0, 0)
+//get_row(game0, 1)
+//get_box(game0, (3,1))
+
+def update(game: String, pos: Int, value: Char): String = game.updated(pos, value)
 
 def toAvoid(game: String, pos: Pos): List[Char] = 
   (get_col(game, pos._1) ++ get_row(game, pos._2) ++ get_box(game, pos))
 
-def candidates(game: String, pos: Pos): List[Char] = 
-  allValues.diff(toAvoid(game,pos))
+def candidates(game: String, pos: Pos): List[Char] = allValues diff toAvoid(game,pos)
 
 //candidates(game0, (0,0))
 
-def pretty(game: String): String = 
-  "\n" + (game sliding (MaxValue, MaxValue) mkString "\n")
+def pretty(game: String): String = "\n" + (game sliding (MaxValue, MaxValue) mkString "\n")
 
 def search(game: String): List[String] = {
   if (isDone(game)) List(game)
-  else {
-    val cs = candidates(game, emptyPosition(game))
-    cs.par.map(c => search(update(game, empty(game), c))).toList.flatten
-  }
+  else 
+    candidates(game, emptyPosition(game)).map(c => search(update(game, empty(game), c))).toList.flatten
 }
 
-search(game0).map(pretty)
 
 val game1 = """23.915...
               |...2..54.
@@ -575,9 +460,8 @@
               |.16..7...
               |...329..1""".stripMargin.replaceAll("\\n", "")
 
-search(game1).map(pretty)
 
-// game that is in the hard(er) category
+// game that is in the hard category
 val game2 = """8........
               |..36.....
               |.7..9.2..
@@ -600,8 +484,8 @@
               |9724...5.""".stripMargin.replaceAll("\\n", "")
 
 
-search(game2).map(pretty)
-search(game3).map(pretty)
+search(game0).map(pretty)
+search(game1).map(pretty)
 
 // for measuring time
 def time_needed[T](i: Int, code: => T) = {
@@ -613,11 +497,10 @@
 
 search(game2).map(pretty)
 search(game3).distinct.length
-time_needed(1, search(game2))
-time_needed(1, search(game3))
+time_needed(3, search(game2))
+time_needed(3, search(game3))
 
 
 
 
-//===================
-// the end for today
+