diff -r 607ceabeeffc -r 029e2862bb4e progs/lecture2.scala --- a/progs/lecture2.scala Mon Nov 11 14:04:22 2019 +0000 +++ b/progs/lecture2.scala Tue Nov 12 00:41:00 2019 +0000 @@ -14,11 +14,16 @@ // String Interpolations //======================= +def cube(n: Int) : Int = n * n * n + val n = 3 -println("The square of " + n + " is " + square(n) + ".") +println("The cube of " + n + " is " + cube(n) + ".") -println(s"The square of ${n} is ${square(n)}.") +println(s"The cube of ${n} is ${cube(n)}.") +// or even + +println(s"The cube of ${n} is ${n * n * n}.") // helpful for debugging purposes // @@ -50,7 +55,6 @@ 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) @@ -92,7 +96,6 @@ get_contents("test.txt") - // operations on options val lst = List(None, Some(1), Some(2), None, Some(3)) @@ -106,7 +109,7 @@ None.isDefined -val ps = List((3, 0), (3, 2), (4, 2), (2, 0), (1, 0), (1, 1)) +val ps = List((3, 0), (4, 2), (6, 2), (2, 0), (1, 0), (1, 1)) // division where possible @@ -121,6 +124,12 @@ for (x <- lst) yield x.getOrElse(0) +// 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 @@ -130,11 +139,10 @@ // // 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 a null, and your +// 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) @@ -154,20 +162,23 @@ val lst = (1 to 10).toList -lst.filter(x => even(x)) -lst.filter(even(_)) lst.filter(even) - lst.count(even) - - lst.find(even) -val ps = List((3, 0), (3, 2), (4, 2), (2, 2), (2, 0), (1, 1), (1, 0)) +lst.filter(x => x % 2 == 0) +lst.filter(_ % 2 == 0) lst.sortWith(_ > _) lst.sortWith(_ < _) +// but this only works when the arguments are clear, but +// not with multiple occurences +lst.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._1 @@ -180,7 +191,6 @@ ps.maxBy(_._2) - // maps (lower-case) //=================== @@ -207,10 +217,9 @@ lst.map(square).filter(_ > 4).map(square) -// lets define our own functions -// type of functions, for example f: Int => Int +// lets define our own higher-order functions +// type of functions is for example Int => Int -lst.tail def my_map_int(lst: List[Int], f: Int => Int) : List[Int] = { if (lst == Nil) Nil @@ -247,7 +256,7 @@ sum_squares(lst) sum_cubes(lst) -// lets try it factorial +// lets try a factorial def fact(n: Int) : Int = if (n == 0) 1 else n * fact(n - 1) @@ -256,30 +265,25 @@ -// if you like verbosity, you can full-specify the literal. -// Don't go telling that to people, though +// sometimes it is needed that you specify the type. (1 to 100).filter((x: Int) => x % 2 == 0).sum -// As x is known to be an Int anyway, you can omit that part +// in this case it is clear that x mist be an Int (1 to 100).filter(x => x % 2 == 0).sum // As each parameter (only x in this case) is passed only once // you can use the wizardy placeholder syntax (1 to 100).filter(_ % 2 == 0).sum -// But if you want to re-use your literal, you can also put it in a value -// In this case, explicit types are required because there's nothing to infer from -val isEven = (x: Int) => x % 2 == 0 -(1 to 100).filter(isEven).sum - -// Option Type again -//=================== +// 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) @@ -294,6 +298,10 @@ lst.flatMap(get_me_an_int).sum +// maps on Options + +get_me_an_int("1234").map(even) +get_me_an_int("12u34").map(even) @@ -303,13 +311,9 @@ // Note the difference between map and Map def factors(n: Int) : List[Int] = - ((1 until n).filter { divisor => - n % divisor == 0 - }).toList - + (2 until n).toList.filter(n % _ == 0) var ls = (1 to 10).toList - val facs = ls.map(n => (n, factors(n))) facs.find(_._1 == 4) @@ -324,16 +328,18 @@ val facsMap = facs.toMap val facsMap0 = facsMap + (0 -> List(1,2,3,4,5)) -facsMap0.get(1) +facsMap0.get(0) -val facsMap4 = facsMap + (1 -> List(1,2,3,4,5)) +val facsMap2 = facsMap + (1 -> List(1,2,3,4,5)) facsMap.get(1) -facsMap4.get(1) +facsMap2.get(1) + +// groupBy function on maps val ls = List("one", "two", "three", "four", "five") ls.groupBy(_.length) -ls.groupBy(_.length).get(2) +ls.groupBy(_.length).get(3) @@ -364,13 +370,11 @@ def my_flatten(xs: List[Option[Int]]): List[Int] = xs match { case Nil => Nil case None::rest => my_flatten(rest) - case Some(v)::foo => { - v :: my_flatten(foo) - } + case Some(v)::rest => v :: my_flatten(rest) } -// another example +// another example with a default case def get_me_a_string(n: Int): String = n match { case 0 | 1 | 2 => "small" case _ => "big" @@ -394,15 +398,13 @@ println(season("foobar")) -// Days of the months +// days of some months def days(month: String) : Int = month match { case "March" | "April" | "May" => 31 case "June" | "July" | "August" => 30 } - - // Silly: fizz buzz def fizz_buzz(n: Int) : String = (n % 3, n % 5) match { case (0, 0) => "fizz buzz" @@ -415,153 +417,40 @@ println(fizz_buzz(n)) -// User-defined Datatypes -//======================== - - -abstract class Colour -case object Red extends Colour -case object Green extends Colour -case object Blue extends Colour - -def fav_colour(c: Colour) : Boolean = c match { - case Red => false - case Green => true - case Blue => false -} - -fav_colour(Green) - - -// ... a tiny bit more useful: Roman Numerals - -abstract class RomanDigit -case object I extends RomanDigit -case object V extends RomanDigit -case object X extends RomanDigit -case object L extends RomanDigit -case object C extends RomanDigit -case object D extends RomanDigit -case object M extends RomanDigit - -type RomanNumeral = List[RomanDigit] - -List(X,I) - -/* -I -> 1 -II -> 2 -III -> 3 -IV -> 4 -V -> 5 -VI -> 6 -VII -> 7 -VIII -> 8 -IX -> 9 -X -> X -*/ - -def RomanNumeral2Int(rs: RomanNumeral): Int = rs match { - case Nil => 0 - case M::r => 1000 + RomanNumeral2Int(r) - case C::M::r => 900 + RomanNumeral2Int(r) - case D::r => 500 + RomanNumeral2Int(r) - case C::D::r => 400 + RomanNumeral2Int(r) - case C::r => 100 + RomanNumeral2Int(r) - case X::C::r => 90 + RomanNumeral2Int(r) - case L::r => 50 + RomanNumeral2Int(r) - case X::L::r => 40 + RomanNumeral2Int(r) - case X::r => 10 + RomanNumeral2Int(r) - case I::X::r => 9 + RomanNumeral2Int(r) - case V::r => 5 + RomanNumeral2Int(r) - case I::V::r => 4 + RomanNumeral2Int(r) - case I::r => 1 + RomanNumeral2Int(r) -} - -RomanNumeral2Int(List(I,V)) // 4 -RomanNumeral2Int(List(I,I,I,I)) // 4 (invalid Roman number) -RomanNumeral2Int(List(V,I)) // 6 -RomanNumeral2Int(List(I,X)) // 9 -RomanNumeral2Int(List(M,C,M,L,X,X,I,X)) // 1979 -RomanNumeral2Int(List(M,M,X,V,I,I)) // 2017 - - -// another example -//================= - -// Once upon a time, in a complete fictional -// country there were Persons... - - -abstract class Person -case object 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 - - -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 -} - -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("\n")) - - -// String interpolations as patterns - -val date = "2000-01-01" -val s"$year-$month-$day" = date - -def parse_date(date: String) = date match { - case s"$year-$month-$day" => Some((year.toInt, month.toInt, day.toInt)) - case s"$day/$month/$year" => Some((year.toInt, month.toInt, day.toInt)) - case _ => None -} // Recursion //=========== -/* a, b, c +// well-known example + +def fib(n: Int) : Int = { + if (n == 0 || n == 1) 1 + else fib(n - 1) + fib(n - 2) +} + -aa aaa -ab baa -ac caa -ba => ...... -bb -bc -ca -cb -cc +/* 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 perms(cs: List[Char], l: Int) : List[String] = { +def combs(cs: List[Char], l: Int) : List[String] = { if (l == 0) List("") - else for (c <- cs; s <- perms(cs, l - 1)) yield s"$c$s" + else for (c <- cs; s <- combs(cs, l - 1)) yield s"$c$s" } -perms("abc".toList, 2) +combs("abc".toList, 2) + + +// another well-known example def move(from: Char, to: Char) = println(s"Move disc from $from to $to!") @@ -575,43 +464,11 @@ } } -hanoi(40, 'A', 'B', 'C') - - -// Tail Recursion -//================ +hanoi(4, 'A', 'B', 'C') -def fact(n: Long): Long = - if (n == 0) 1 else n * fact(n - 1) - -fact(10) //ok -fact(10000) // produces a stackoverflow - -def factT(n: BigInt, acc: BigInt): BigInt = - if (n == 0) acc else factT(n - 1, n * acc) - -factT(10, 1) -factT(100000, 1) - -// there is a flag for ensuring a function is tail recursive -import scala.annotation.tailrec - -@tailrec -def factT(n: BigInt, acc: BigInt): BigInt = - if (n == 0) acc else factT(n - 1, n * acc) - - - -// for tail-recursive functions the Scala compiler -// generates loop-like code, which does not need -// to allocate stack-space in each recursive -// call; Scala can do this only for tail-recursive -// functions - - -// A Web Crawler / Email Harvester -//================================= +// A Recursive Web Crawler / Email Harvester +//=========================================== // // the idea is to look for links using the // regular expression "https?://[^"]*" and for @@ -643,13 +500,11 @@ // naive version of crawl - searches until a given depth, // visits pages potentially more than once -def crawl(url: String, n: Int) : Set[String] = { - if (n == 0) Set() +def crawl(url: String, n: Int) : Unit = { + if (n == 0) () 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 crawl(u, n - 1)).flatten + for (u <- get_all_URLs(get_page(url))) crawl(u, n - 1) } } @@ -659,130 +514,25 @@ crawl(startURL, 2) - - - - - -// Sudoku -//======== - -// THE POINT OF THIS CODE IS NOT TO BE SUPER -// EFFICIENT AND FAST, just explaining exhaustive -// depth-first search - - -val game0 = """.14.6.3.. - |62...4..9 - |.8..5.6.. - |.6.2....3 - |.7..1..5. - |5....9.6. - |..6.2..3. - |1..5...92 - |..7.9.41.""".stripMargin.replaceAll("\\n", "") +// a primitive email harvester +def 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 + } +} -type Pos = (Int, Int) -val emptyValue = '.' -val maxValue = 9 - -val allValues = "123456789".toList -val indexes = (0 to 8).toList - - -def empty(game: String) = game.indexOf(emptyValue) -def isDone(game: String) = empty(game) == -1 -def emptyPosition(game: String) : Pos = - (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 get_box(game: String, pos: Pos): List[Char] = { - def base(p: Int): Int = (p / 3) * 3 - val x0 = base(pos._1) - val y0 = base(pos._2) - for (x <- (x0 until x0 + 3).toList; - y <- (y0 until y0 + 3).toList) yield game(x + y * maxValue) -} +emails(startURL, 3) -//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)) - -//candidates(game0, (0, 0)) - -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))).flatten -} - -// an easy game -val game1 = """23.915... - |...2..54. - |6.7...... - |..1.....9 - |89.5.3.17 - |5.....6.. - |......9.5 - |.16..7... - |...329..1""".stripMargin.replaceAll("\\n", "") - - -// a game that is in the sligtly harder category -val game2 = """8........ - |..36..... - |.7..9.2.. - |.5...7... - |....457.. - |...1...3. - |..1....68 - |..85...1. - |.9....4..""".stripMargin.replaceAll("\\n", "") - -// a game with multiple solutions -val game3 = """.8...9743 - |.5...8.1. - |.1....... - |8....5... - |...8.4... - |...3....6 - |.......7. - |.3.5...8. - |9724...5.""".stripMargin.replaceAll("\\n", "") - - -search(game0).map(pretty) -search(game1).map(pretty) - -// for measuring time -def time_needed[T](i: Int, code: => T) = { - val start = System.nanoTime() - for (j <- 1 to i) code - val end = System.nanoTime() - s"${(end - start) / i / 1.0e9} secs" -} - -search(game2).map(pretty) -search(game3).distinct.length -time_needed(3, search(game2)) -time_needed(3, search(game3)) +// 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._