progs/lecture2.scala
changeset 147 72f7dd1a3754
parent 95 4fa7231fede7
child 148 ead6089209ba
--- a/progs/lecture2.scala	Tue Nov 14 22:19:04 2017 +0000
+++ b/progs/lecture2.scala	Fri Nov 17 02:13:40 2017 +0000
@@ -2,10 +2,82 @@
 //=================
 
 
+// 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) 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),
+// 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:
+
+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
@@ -15,15 +87,7 @@
 List(5,6,7,8,9).find(_ < 4)
 
 
-// Values in types
-//
-// Boolean: 
-// Int: 
-// String: 
-//
-// Option[String]:
-//   
-
+// some operations on Option's
 
 val lst = List(None, Some(1), Some(2), None, Some(3))
 
@@ -40,38 +104,54 @@
   if (y == 0) None else Some(x / y)
 }
 
-// getOrElse is for setting a default value
+// use .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 Option (no exceptions)
+// error handling with Options (no exceptions)
+//
+//  Try(....)
 //
 //  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
 
-Source.fromURL("""http://www.inf.kcl.ac.uk/staff/urbanc/""").mkString
+val my_url = """https://nms.kcl.ac.uk/christian.urban"""
+
+Source.fromURL(my_url).mkString
 
-Try(Source.fromURL("""http://www.inf.kcl.ac.uk/staff/urbanc/""").mkString).getOrElse("")
+Try(Source.fromURL(my_url).mkString).getOrElse("")
 
-Try(Some(Source.fromURL("""http://www.inf.kcl.ac.uk/staff/urbanc/""").mkString)).getOrElse(None)
+Try(Some(Source.fromURL(my_url).mkString)).getOrElse(None)
+
 
 // a function that turns strings into numbers
-Integer.parseInt("12u34")
+Integer.parseInt("1234")
+
 
 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 = lst.flatMap(get_me_an_int(_)).sum
+val sum = (for (i <- lst) yield get_me_an_int(i)).flatten.sum
 
 
 // This may not look any better than working with null in Java, but to
@@ -80,12 +160,11 @@
 // write that function.
 //
 // In Java, if you didn't write this function, you'd have to depend on
-// the Javadoc of the get_me_an_int. If you didn't look at the Javadoc, 
+// the Javadoc of 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(7)
@@ -93,27 +172,166 @@
 
 
 
+// Higher-Order Functions
+//========================
+
+// functions can take functions as arguments
+
+val lst = (1 to 10).toList
+
+def even(x: Int) : Boolean = x % 2 == 0
+def odd(x: Int) : Boolean = x % 2 == 1
+
+lst.filter(x => even(x))
+lst.filter(even(_))
+lst.filter(even)
+
+lst.find(_ > 8)
+
+// map applies a function to each element of a list
+
+def square(x: Int): Int = x * x
+
+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)
+
+
+// Why could functions as arguments be 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)
+
+
+sumInt(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)
+
+
+// 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)
+
+
+
+// You can see the pattern....can we simplify out 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 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)
+
+
+
+// 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)
+
+
+// other function types
+//
+// f1: (Int, Int) => Int
+// f2: List[String] => Option[Int]
+// ... 
+
+
+// Function Composition
+//======================
+
+// How could 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
+
+// they compose nicely
+valid_msg(add_footer("Hello World"))
+valid_msg(duplicate(add_footer("Hello World")))
+
+
+// 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
+}
+
+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(""))
+
+
+// Avoid always null!
+def better_first_word(msg: String) : Option[String] = {
+  val words = msg.split(" ")
+  if (words(0) != "") Some(words(0)) else None
+}
+
+better_first_word("Hello World").map(duplicate)
+better_first_word("Hello World").map(duplicate).map(duplicate).map(valid_msg)
+
+better_first_word("").map(duplicate)
+better_first_word("").map(duplicate).map(valid_msg)
+
+
+
 
 // Type abbreviations
 //====================
 
 // some syntactic convenience
+
 type Pos = (int, Int)
-
 type Board = List[List[Int]]
 
 
 
-// Implicits
-//===========
+// Implicits (Cool Feature)
+//=========================
 //
-// for example adding your own methods to Strings:
-// imagine you want to increment strings, like
+// 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
+// using implicit conversions.
 
 
 implicit class MyString(s: String) {
@@ -123,10 +341,11 @@
 "HAL".increment
 
 
-// No return in Scala
+
+// No returns in Scala
 //====================
 
-//You should not use "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 
@@ -136,20 +355,21 @@
 def sq2(x: Int): Int = return x * x
 
 def sumq(ls: List[Int]): Int = {
-  (for (x <- ls) yield (return x * x)).sum[Int]
+  ls.map(sq1).sum[Int]
 }
 
-sumq(List(1,2,3,4))
+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
+
+def sumq(ls: List[Int]): Int = {
+  val sqs : List[Int] = for (x <- ls) yield (return x * x)
+  sqs.sum
 }
 
 
 
+
 // Pattern Matching
 //==================
 
@@ -179,7 +399,6 @@
 
 
 
-
 def my_flatten(lst: List[Option[Int]]): List[Int] = lst match {
   case Nil => Nil
   case None::xs => my_flatten(xs)
@@ -211,47 +430,34 @@
 
 println(season("foobar"))
 
+
 // User-defined Datatypes
 //========================
 
-abstract class Tree
-case class Node(elem: Int, left: Tree, right: Tree) extends Tree
-case class Leaf() extends Tree
-
+abstract class Colour
+case class Red() extends Colour 
+case class Green() extends Colour 
+case class Blue() extends Colour
 
-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))
+def fav_colour(c: Colour) : Boolean = c match {
+  case Red()   => false
+  case Green() => true
+  case Blue()  => false 
 }
 
 
-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
-}
+// actually this can be written with "object"
 
 
-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
+
 
 def title(p: Person): String = p match {
   case King() => "His Majesty the King"
@@ -260,6 +466,7 @@
   case Peasant(name) => name
 }
 
+
 def superior(p1: Person, p2: Person): Boolean = (p1, p2) match {
   case (King(), _) => true
   case (Peer(_,_,_), Knight(_)) => true
@@ -281,95 +488,48 @@
 
 
 
-// Higher-Order Functions
-//========================
-
-// functions can take functions as arguments
-
-val lst = (1 to 10).toList
-
-def even(x: Int): Boolean = x % 2 == 0
-def odd(x: Int): Boolean = x % 2 == 1
-
-lst.filter(x => even(x))
-lst.filter(even(_))
-lst.filter(even)
-
-lst.find(_ > 8)
-
-def square(x: Int): Int = x * x
-
-lst.map(square)
-
-lst.map(square).filter(_ > 4)
-
-lst.map(square).filter(_ > 4).map(square)
-
-// in my collatz.scala
-//(1 to bnd).map(i => (collatz(i), i)).maxBy(_._1)
-
-
-// type of functions, for example f: Int => Int
-
-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)
-}
-
-my_map_int(lst, square)
-
-// other function types
-//
-// f1: (Int, Int) => Int
-// f2: List[String] => Option[Int]
-// ... 
-
-
-def sumOf(f: Int => Int, lst: List[Int]): Int = lst match {
-  case Nil => 0
-  case x::xs => f(x) + sumOf(f, xs)
-}
-
-def sum_squares(lst: List[Int]) = sumOf(square, lst)
-def sum_cubes(lst: List[Int])   = sumOf(x => x * x * x, lst)
-
-sum_squares(lst)
-sum_cubes(lst)
-
-// lets try it factorial
-def fact(n: Int): Int = ...
-
-def sum_fact(lst: List[Int]) = sumOf(fact, lst)
-sum_fact(lst)
-
-// Avoid being mutable
-//=====================
-
-// a student showed me...
-import scala.collection.mutable.ListBuffer
 
 
 
-def collatz_max(bnd: Long): (Long, Long) = {
-  val colNos = ListBuffer[(Long, Long)]()
-  for (i <- (1L to bnd).toList) colNos += ((collatz(i), i))
-  colNos.max
-}
+// Problems with mutability and parallel computations
+//====================================================
 
-def collatz_max(bnd: Long): (Long, Long) = {
-  (1L to bnd).map((i) => (collatz(i), i)).maxBy(_._1)
+def count_intersection(A: Set[Int], B: Set[Int]) : Int = {
+  var count = 0
+  for (x <- A; if (B contains x)) count += 1 
+  count
 }
 
-//views -> lazy collection
-def collatz_max(bnd: Long): (Long, Long) = {
-  (1L to bnd).view.map((i) => (collatz(i), i)).maxBy(_._1)
+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
+
+
+//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
 }
 
-// raises a GC exception
-(1 to 1000000000).filter(_ % 2 == 0).take(10).toList
-// ==> java.lang.OutOfMemoryError: GC overhead limit exceeded
+val A = (1 to 1000000).toSet
+val B = (1 to 1000000 by 4).toSet
 
-(1 to 1000000000).view.filter(_ % 2 == 0).take(10).toList
+time_needed(10, count_intersection(A, B))
+time_needed(10, count_intersection2(A, B))
+
+
 
 
 
@@ -399,8 +559,6 @@
 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)
@@ -409,6 +567,9 @@
 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))
 
+get_row(game0, 3)
+get_col(game0, 0)
+
 def get_box(game: String, pos: Pos): List[Char] = {
     def base(p: Int): Int = (p / 3) * 3
     val x0 = base(pos._1)
@@ -417,28 +578,32 @@
     (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))
 
-//get_row(game0, 0)
-//get_row(game0, 1)
-//get_box(game0, (3,1))
-
+// this is not mutable!!
 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 
-    candidates(game, emptyPosition(game)).map(c => search(update(game, empty(game), c))).toList.flatten
+  else {
+    val cs = candidates(game, emptyPosition(game))
+    cs.map(c => search(update(game, empty(game), c))).toList.flatten
+  }
 }
 
+search(game0).map(pretty)
 
 val game1 = """23.915...
               |...2..54.
@@ -450,8 +615,9 @@
               |.16..7...
               |...329..1""".stripMargin.replaceAll("\\n", "")
 
+search(game1).map(pretty)
 
-// game that is in the hard category
+// game that is in the hard(er) category
 val game2 = """8........
               |..36.....
               |.7..9.2..
@@ -474,8 +640,8 @@
               |9724...5.""".stripMargin.replaceAll("\\n", "")
 
 
-search(game0).map(pretty)
-search(game1).map(pretty)
+search(game2).map(pretty)
+search(game3).map(pretty)
 
 // for measuring time
 def time_needed[T](i: Int, code: => T) = {
@@ -487,8 +653,8 @@
 
 search(game2).map(pretty)
 search(game3).distinct.length
-time_needed(3, search(game2))
-time_needed(3, search(game3))
+time_needed(1, search(game2))
+time_needed(1, search(game3))