// Scala Lecture 2//=================// For-Comprehensions Again//==========================// the first produces a result, while the second does notfor (n <- List(1, 2, 3, 4, 5)) yield n * nfor (n <- List(1, 2, 3, 4, 5)) println(n)// 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)// 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)// 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).mkStringTry(Source.fromURL(my_url).mkString).getOrElse("")Try(Some(Source.fromURL(my_url).mkString)).getOrElse(None)// the same for filesTry(Some(Source.fromFile("text.txt").mkString)).getOrElse(None)// how to implement a function for reading // (lines) something from files...//def get_contents(name: String) : List[String] = Source.fromFile(name).getLines.toListget_contents("text.txt")get_contents("test.txt")// slightly better - return Nildef get_contents(name: String) : List[String] = Try(Source.fromFile(name).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).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.isDefinedval 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))for (x <- lst) yield x.getOrElse(0)// a function that turns strings into numbers (similar to .toInt)Integer.parseInt("12u34")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.reverse.sortedlst.filter(even)lst.count(odd)lst.find(even)lst.exists(even)lst.filter(_ < 4) lst.filter(x => x % 2 == 1)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)val 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(x => (double(x), square(x)))lst.map(square)// this is actually how for-comprehensions are// defined in Scalalst.map(n => square(n))for (n <- lst) yield square(n)// this can be iteratedlst.map(square).filter(_ > 4)(lst.map(square) .filter(_ > 4) .map(square))// lets define our own higher-order functions// type of functions is for example Int => Int0 :: List(3,4,5,6)def 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)}// other function types//// f1: (Int, Int) => Int// f2: List[String] => Option[Int]// ... val lst = (1 to 10).toListlst.sumval lst = List(1,2,3,4)lst.headlst.taildef sumOf(f: Int => Int, lst: List[Int]): Int = lst match { case Nil => 0 case x::foo => f(x) + sumOf(f, foo)}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 a factorialdef fact(n: Int) : Int = if (n == 0) 1 else n * fact(n - 1)def sum_fact(lst: List[Int]) = sumOf(fact, lst)sum_fact(lst)// sometimes it is needed that you specify the type. (1 to 100).filter((x: Int) => x % 2 == 0).sum // in this case it is clear that x must be an Int(1 to 100).filter(x => x % 2 == 0).sum// When each parameter (only x in this case) is used only once// you can use the wizardy placeholder syntax(1 to 100).filter(_ % 2 == 0).sum// Option Type and maps//======================// a function that turns strings into numbers (similar to .toInt)Integer.parseInt("12u34")import scala.util._def get_me_an_int(s: String) : Option[Int] = Try(Some(Integer.parseInt(s))).getOrElse(None)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.sumlst.map(get_me_an_int).flatten.sumlst.flatMap(get_me_an_int).sum// maps on Optionsget_me_an_int("12345").map(even)get_me_an_int("12u34").map(even)def my_map_option(o: Option[Int], f : Int => Int) : Option[Int] = {o match { case None => None case Some(foo) => Some(f(foo))}}my_map_option(Some(4), square)my_map_option(None, square)// Map type (upper-case)//=======================// Note the difference between map and Mapval ascii = ('a' to 'z').map(c => (c, c.toInt)).toListval ascii_Map = ascii.toMapdef factors(n: Int) : List[Int] = (2 until n).toList.filter(n % _ == 0)var ls = (1 to 10).toListval facs = ls.map(n => (n, factors(n)))facs.find(_._1 == 4)// works for lists of pairsfacs.toMapfacs.toMap.get(40)facs.toMap.getOrElse(42, Nil)val facsMap = facs.toMapval facsMap0 = facsMap + (0 -> List(1,2,3,4,5))facsMap0.get(0)val facsMap2 = facsMap + (1 -> List(1,2,3,4,5))facsMap.get(1)facsMap2.get(1)// groupBy function on Mapsval ls = List("one", "two", "three", "four", "five")ls.groupBy(_.length)ls.groupBy(_.length).get(5)// 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// }// recallval 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)))// another example with a default casedef get_me_a_string(n: Int): String = n match { case 0 | 1 | 2 => "small"}get_me_a_string(3)// 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"}println(season("November"))// What happens if no case matches?println(season("foobar"))// days of some monthsdef days(month: String) : Int = month match { case "March" | "April" | "May" => 31 case "June" | "July" | "August" => 30}// 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 <- 0 to 20) println(fizz_buzz(n))// Recursion//===========// well-known exampledef fib(n: Int) : Int = { if (n == 0 || n == 1) 1 else fib(n - 1) + fib(n - 2)}/* 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)// 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 (xs, 0) => Nil case (x::xs, n) => (x::xs) :: moves(xs, n - 1)}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))// 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)