// Scala Lecture 2//=================// String Interpolations//=======================def cube(n: Int) : Int = n * n * nval n = 3println("The cube of " + n + " is " + cube(n) + ".")println(s"The cube of $n is ${cube(n)}.")// or evenprintln(s"The cube of $n is ${n * n * n}.")// helpful for debugging purposes//// "The most effective debugging tool is still careful // thought, coupled with judiciously placed print // statements."// — Brian W. Kernighan, in Unix for Beginners (1979)def gcd_db(a: Int, b: Int) : Int = { println(s"Function called with $a and $b.") if (b == 0) a else gcd_db(b, a % b)}gcd_db(48, 18)// you can also implement your own string interpolationsimport scala.language.implicitConversionsimport scala.language.reflectiveCallsimplicit def sring_inters(sc: StringContext) = new { def i(args: Any*): String = s"\t${sc.s(args:_*)}\n" def l(args: Any*): String = s"${sc.s(args:_*)}:\n"}// this allows you to write things likei"add ${3+2}" l"some_fresh_name"// The Option Type//=================// in Java, if something unusually happens, you return null // or raise an exception////in Scala you use Options instead// - if the value is present, you use Some(value)// - if no value is present, you use NoneList(7,2,3,4,5,6).find(_ < 4)List(5,6,7,8,9).find(_ < 4)// Int: ..., 0, 1, 2,...// Boolean: true false//// List[Int]: Nil, List(_) //// Option[Int]: None, Some(0), Some(1), ...// Option[Boolean]: None, Some(true), Some(false)// Option[...]: None, Some(_)def safe_div(x: Int, y: Int) : Option[Int] = if (y == 0) None else Some(x / y)safe_div(10 + 5, 4 - 1) List(1,2,3,4,5,6).indexOf(7)List[Int]().minOptiondef my_min(ls: List[Int]) : Option[Int] = ls.minOptionmy_min(List(1,2,3,4))// better error handling with Options (no exceptions)//// Try(something).getOrElse(what_to_do_in_case_of_an_exception)//import scala.util._import io.Sourceval my_url = "https://nms.kcl.ac.uk/christian.urban/"Source.fromURL(my_url)("ISO-8859-1").mkStringSource.fromURL(my_url)("ISO-8859-1").getLines().toListTry(Source.fromURL(my_url)("ISO-8859-1").mkString).getOrElse("")Try(Some(Source.fromURL(my_url)("ISO-8859-1").mkString)).getOrElse(None)// the same for filesTry(Some(Source.fromFile("test.txt")("ISO-8859-1").mkString)).getOrElse(None)Try(Source.fromFile("test.txt")("ISO-8859-1").mkString).toOptionUsing(Source.fromFile("test.txt")("ISO-8859-1"))(_.mkString).toOption// how to implement a function for reading // (lines) from files...//def get_contents(name: String) : List[String] = Source.fromFile(name)("ISO-8859-1").getLines().toListget_contents("text.txt")get_contents("test.txt")// slightly better - return Nildef get_contents(name: String) : List[String] = Try(Source.fromFile(name)("ISO-8859-1").getLines.toList).getOrElse(List())get_contents("text.txt")// much better - you record in the type that things can go wrong def get_contents(name: String) : Option[List[String]] = Try(Some(Source.fromFile(name)("ISO-8859-1").getLines().toList)).getOrElse(None)get_contents("text.txt")get_contents("test.txt")// operations on optionsval lst = List(None, Some(1), Some(2), None, Some(3))lst.flattenSome(1).getNone.getSome(1).isDefinedNone.isDefinedfor (x <- lst) yield x.getOrElse(0)val ps = List((3, 0), (4, 2), (6, 2), (2, 0), (1, 0), (1, 1))// division where possiblefor ((x, y) <- ps) yield { if (y == 0) None else Some(x / y)}// getOrElse is for setting a default valueval lst = List(None, Some(1), Some(2), None, Some(3))// a function that turns strings into numbers // (similar to .toInt)Integer.parseInt("1234")def get_me_an_int(s: String) : Option[Int] = Try(Some(Integer.parseInt(s))).getOrElse(None)// This may not look any better than working with null in Java, but to// see the value, you have to put yourself in the shoes of the// consumer of the get_me_an_int function, and imagine you didn't// 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, // you might not know that get_me_an_int could return 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)List(5,6,7,8,9).indexOf(10)List(5,6,7,8,9)(-1)Try({ val x = 3 val y = 0 Some(x / y)}).getOrElse(None)// minOption // maxOption // minByOption // maxByOption// Higher-Order Functions//========================// functions can take functions as arguments// and produce functions as resultdef even(x: Int) : Boolean = x % 2 == 0def odd(x: Int) : Boolean = x % 2 == 1val lst = (1 to 10).toListlst.filter(even)lst.count(odd)lst.find(even)lst.exists(even)lst.find(_ < 4)lst.filter(_ < 4) def less4(x: Int) : Boolean = x < 4lst.find(less4)lst.find(_ < 4)lst.filter(x => x % 2 == 0)lst.filter(_ % 2 == 0)lst.sortWith((x, y) => x < y)lst.sortWith(_ > _)// but this only works when the arguments are clear, but // not with multiple occurenceslst.find(n => odd(n) && n > 2)// lexicographic orderingval ps = List((3, 0), (3, 2), (4, 2), (2, 2), (2, 0), (1, 1), (1, 0))def lex(x: (Int, Int), y: (Int, Int)) : Boolean = if (x._1 == y._1) x._2 < y._2 else x._1 < y._1ps.sortWith(lex)ps.sortBy(x => x._1)ps.sortBy(_._2)ps.maxBy(_._1)ps.maxBy(_._2)// maps (lower-case)//===================def double(x: Int): Int = x + xdef square(x: Int): Int = x * xval lst = (1 to 10).toListlst.map(square)lst.map(x => (double(x), square(x)))// works also for stringsdef tweet(c: Char) = c.toUpper"Hello World".map(tweet)// this can be iteratedlst.map(square).filter(_ > 4)lst.map(square).find(_ > 4)lst.map(square).find(_ > 4).map(double)lst.map(square) .find(_ > 4) .map(double)// Option Type and maps//======================// a function that turns strings into numbers (similar to .toInt)Integer.parseInt("12u34")// maps on Optionsimport scala.util._def get_me_an_int(s: String) : Option[Int] = Try(Some(Integer.parseInt(s))).getOrElse(None)get_me_an_int("12345").map(_ % 2 == 0)get_me_an_int("12u34").map(_ % 2 == 0)val lst = List("12345", "foo", "5432", "bar", "x21", "456")for (x <- lst) yield get_me_an_int(x)// summing up all the numberslst.map(get_me_an_int).flatten.sum// this is actually how for-comprehensions are// defined in Scalalst.map(n => square(n))for (n <- lst) yield square(n)// lets define our own higher-order functions// type of functions is for example Int => Intdef my_map_int(lst: List[Int], f: Int => Int) : List[Int] = { if (lst == Nil) Nil else f(lst.head) :: my_map_int(lst.tail, f)}my_map_int(lst, square)// same function using pattern matching: a kind// of switch statement on steroids (see more later on)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) }val biglst = (1 to 10000).toListmy_map_int(biglst, double)(1 to 10000000).toList.map(double)// other function types//// f1: (Int, Int) => Int// f2: List[String] => Option[Int]// ... // Map type (upper-case)//=======================// Note the difference between map and Mapval m = Map(1 -> "one", 2 -> "two", 10 -> "many")List((1, "one"), (2, "two"), (10, "many")).toMapm.get(1)m.get(4)m.getOrElse(1, "")m.getOrElse(4, "")val new_m = m + (10 -> "ten")new_m.get(10)val m2 = for ((k, v) <- m) yield (k, v.toUpperCase)// groupBy function on Mapsval lst = List("one", "two", "three", "four", "five")lst.groupBy(_.head)lst.groupBy(_.length)lst.groupBy(_.length).get(3)val grps = lst.groupBy(_.length)grps.keySet// 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// }// recalldef 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 my_map_option(o: Option[Int], f: Int => Int) : Option[Int] = o match { case None => None case Some(x) => Some(f(x)) }my_map_option(None, x => x * x)my_map_option(Some(8), x => x * x)// you can also have cases combineddef season(month: String) : 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" => "It's winter" case "January" | "February" => "It's unfortunately winter" case _ => "Wrong month"}// pattern-match on integersdef fib(n: Int) : Int = n match { case 0 | 1 => 1 case n => fib(n - 1) + fib(n - 2)}fib(10)// Silly: fizz buzzdef 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 <- 1 to 20) println(fizz_buzz(n))val lst = List(None, Some(1), Some(2), None, Some(3)).flattendef my_flatten(xs: List[Option[Int]]): List[Int] = xs match { case Nil => Nil case None::rest => my_flatten(rest) case Some(v)::rest => v :: my_flatten(rest) }my_flatten(List(None, Some(1), Some(2), None, Some(3)))// Recursion//===========/* Say you have characters a, b, c. What are all the combinations of a certain length? All combinations of length 2: aa, ab, ac, ba, bb, bc, ca, cb, cc Combinations of length 3: aaa, baa, caa, and so on......*/def combs(cs: List[Char], n: Int) : List[String] = { if (n == 0) List("") else for (c <- cs; s <- combs(cs, n - 1)) yield s"$c$s"}combs(List('a', 'b', 'c'), 3)def combs(cs: List[Char], l: Int) : List[String] = { if (l == 0) List("") else for (c <- cs; s <- combs(cs, l - 1)) yield s"$c$s"}combs("abc".toList, 2)// When writing recursive functions you have to// think about three points// // - How to start with a recursive function// - How to communicate between recursive calls// - Exit conditions// A Recursive Web Crawler / Email Harvester//===========================================//// the idea is to look for links using the// regular expression "https?://[^"]*" and for// email addresses using another regex.import io.Sourceimport scala.util._// gets the first 10K of a web-pagedef get_page(url: String) : String = { Try(Source.fromURL(url)("ISO-8859-1").take(10000).mkString). getOrElse { println(s" Problem with: $url"); ""}}// regex for URLs and emailsval http_pattern = """"https?://[^"]*"""".rval email_pattern = """([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})""".r//test case://email_pattern.findAllIn// ("foo bla christian@kcl.ac.uk 1234567").toList// drops the first and last character from a stringdef unquote(s: String) = s.drop(1).dropRight(1)def get_all_URLs(page: String): Set[String] = http_pattern.findAllIn(page).map(unquote).toSet// naive version of crawl - searches until a given depth,// visits pages potentially more than oncedef crawl(url: String, n: Int) : Unit = { if (n == 0) () else { println(s" Visiting: $n $url") for (u <- get_all_URLs(get_page(url))) crawl(u, n - 1) }}// some starting URLs for the crawlerval startURL = """https://nms.kcl.ac.uk/christian.urban/"""crawl(startURL, 2)// a primitive email harvesterdef emails(url: String, n: Int) : Set[String] = { if (n == 0) Set() else { println(s" Visiting: $n $url") val page = get_page(url) val new_emails = email_pattern.findAllIn(page).toSet new_emails ++ (for (u <- get_all_URLs(page)) yield emails(u, n - 1)).flatten }}emails(startURL, 3)// if we want to explore the internet "deeper", then we// first have to parallelise the request of webpages://// scala -cp scala-parallel-collections_2.13-0.2.0.jar // import scala.collection.parallel.CollectionConverters._// Jumping Towers//================def moves(xs: List[Int], n: Int) : List[List[Int]] = (xs, n) match { case (Nil, _) => Nil case (_, 0) => Nil case (x::xs, n) => (x::xs) :: moves(xs, n - 1) }// List(5,5,1,0) -> moves(List(5,1,0), 5)moves(List(5,1,0), 1)moves(List(5,1,0), 2)moves(List(5,1,0), 5)// checks whether a jump tour exists at alldef search(xs: List[Int]) : Boolean = xs match { case Nil => true case x::xs => if (xs.length < x) true else moves(xs, x).exists(search(_))}search(List(5,3,2,5,1,1))search(List(3,5,1,0,0,0,1))search(List(3,5,1,0,0,0,0,1))search(List(3,5,1,0,0,0,1,1))search(List(3,5,1))search(List(5,1,1))search(Nil)search(List(1))search(List(5,1,1))search(List(3,5,1,0,0,0,0,0,0,0,0,1))import scala.util._List.fill(100)(Random.nextInt(2))search(List.fill(100)(Random.nextInt(10)))// generate *all* jump tours// if we are only interested in the shortes one, we could// shortcircut the calculation and only return List(x) in// case where xs.length < x, because no tour can be shorter// than 1// def jumps(xs: List[Int]) : List[List[Int]] = xs match { case Nil => Nil case x::xs => { val children = moves(xs, x) val results = children.map(cs => jumps(cs).map(x :: _)).flatten if (xs.length < x) List(x) :: results else results }}jumps(List(3,5,1,2,1,2,1))jumps(List(3,5,1,2,3,4,1))jumps(List(3,5,1,0,0,0,1))jumps(List(3,5,1))jumps(List(5,1,1))jumps(Nil)jumps(List(1))jumps(List(5,1,2))moves(List(1,2), 5)jumps(List(1,5,1,2))jumps(List(3,5,1,0,0,0,0,0,0,0,0,1))jumps(List(5,3,2,5,1,1)).minBy(_.length)jumps(List(1,3,5,8,9,2,6,7,6,8,9)).minBy(_.length)jumps(List(1,3,6,1,0,9)).minBy(_.length)jumps(List(2,3,1,1,2,4,2,0,1,1)).minBy(_.length)