--- 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
+