diff -r 937c995b047a -r a112e0e2325c progs/lecture2.scala --- 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 +