diff -r b84ea52bfd8f -r cdfb2ce30a3d progs/lecture3.scala --- a/progs/lecture3.scala Tue Nov 12 10:47:27 2019 +0000 +++ b/progs/lecture3.scala Tue Nov 19 00:40:27 2019 +0000 @@ -1,6 +1,16 @@ // Scala Lecture 3 //================= +// - last week +// +// option type +// higher-order function + + + +// Recursion Again ;o) +//==================== + // A Web Crawler / Email Harvester //================================= @@ -15,7 +25,7 @@ // gets the first 10K of a web-page def get_page(url: String) : String = { Try(Source.fromURL(url)("ISO-8859-1").take(10000).mkString). - getOrElse { println(s" Problem with: $url"); ""} + getOrElse { println(s" Problem with: $url"); ""} } // regex for URLs and emails @@ -31,345 +41,61 @@ def get_all_URLs(page: String): Set[String] = http_pattern.findAllIn(page).map(unquote).toSet -// naive version of crawl - searches until a given depth, +// a 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).par) yield crawl(u, n - 1)).flatten + for (u <- get_all_URLs(get_page(url))) crawl(u, n - 1) } } // some starting URLs for the crawler val startURL = """https://nms.kcl.ac.uk/christian.urban/""" + crawl(startURL, 2) -// 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 -} - - - - -// User-defined Datatypes and Pattern Matching -//============================================= - - -abstract class Exp -case class N(n: Int) extends Exp // for numbers -case class Plus(e1: Exp, e2: Exp) extends Exp -case class Times(e1: Exp, e2: Exp) extends Exp - -def string(e: Exp) : String = e match { - case N(n) => n.toString - case Plus(e1, e2) => "(" + string(e1) + " + " + string(e2) + ")" - case Times(e1, e2) => "(" + string(e1) + " * " + string(e2) + ")" -} - -val e = Plus(N(9), Times(N(3), N(4))) -println(string(e)) - -def eval(e: Exp) : Int = e match { - case N(n) => n - case Plus(e1, e2) => eval(e1) + eval(e2) - case Times(e1, e2) => eval(e1) * eval(e2) -} - -def simp(e: Exp) : Exp = e match { - case N(n) => N(n) - case Plus(e1, e2) => (simp(e1), simp(e2)) match { - case (N(0), e2s) => e2s - case (e1s, N(0)) => e1s - case (e1s, e2s) => Plus(e1s, e2s) - } - case Times(e1, e2) => (simp(e1), simp(e2)) match { - case (N(0), _) => N(0) - case (_, N(0)) => N(0) - case (N(1), e2s) => e2s - case (e1s, N(1)) => e1s - case (e1s, e2s) => Times(e1s, e2s) - } +// 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 + } } -println(eval(e)) - -val e2 = Times(Plus(N(0), N(1)), Plus(N(0), N(9))) -println(string(e2)) -println(string(simp(e2))) +emails(startURL, 2) -// Tokens and Reverse Polish Notation -abstract class Token -case class T(n: Int) extends Token -case object PL extends Token -case object TI extends Token -def rp(e: Exp) : List[Token] = e match { - case N(n) => List(T(n)) - case Plus(e1, e2) => rp(e1) ::: rp(e2) ::: List(PL) - case Times(e1, e2) => rp(e1) ::: rp(e2) ::: List(TI) -} -println(string(e2)) -println(rp(e2)) - -def comp(ls: List[Token], st: List[Int]) : Int = (ls, st) match { - case (Nil, st) => st.head - case (T(n)::rest, st) => comp(rest, n::st) - case (PL::rest, n1::n2::st) => comp(rest, n1 + n2::st) - case (TI::rest, n1::n2::st) => comp(rest, n1 * n2::st) -} - -comp(rp(e), Nil) - -def proc(s: String) : Token = s match { - case "+" => PL - case "*" => TI - case _ => T(s.toInt) -} - -comp("1 2 + 4 * 5 + 3 +".split(" ").toList.map(proc), Nil) - +// 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._ -def string(e: Exp) : String = e match { - case N(n) => n.toString - case Plus(e1, e2) => "(" + string(e1) + " + " + string(e2) + ")" - case Times(e1, e2) => "(" + string(e1) + " * " + string(e2) + ")" -} - -val e = Plus(N(9), Times(N(3), N(4))) - -println(string(e)) - -def eval(e: Exp) : Int = e match { - case N(n) => n - case Plus(e1, e2) => eval(e1) + eval(e2) - case Times(e1, e2) => eval(e1) * eval(e2) -} - -eval(e) +// another well-known example +//============================ -def simp(e: Exp) : Exp = e match { - case N(n) => N(n) - case Plus(e1, e2) => (simp(e1), simp(e2)) match { - case (N(0), e2s) => e2s - case (e1s, N(0)) => e1s - case (e1s, e2s) => Plus(e1s, e2s) - } - case Times(e1, e2) => (simp(e1), simp(e2)) match { - case (N(0), e2s) => N(0) - case (e1s, N(0)) => N(0) - case (N(1), e2s) => e2s - case (e1s, N(1)) => e1s - case (e1s, e2s) => Times(e1s, e2s) - } -} - - -val e2 = Times(Plus(N(0), N(1)), Plus(N(0), N(9))) -println(string(e2)) -println(string(simp(e2))) - -// Token and Reverse Polish Notation -abstract class Token -case class T(n: Int) extends Token -case object PL extends Token -case object TI extends Token - -def rp(e: Exp) : List[Token] = e match { - case N(n) => List(T(n)) - case Plus(e1, e2) => rp(e1) ::: rp(e2) ::: List(PL) - case Times(e1, e2) => rp(e1) ::: rp(e2) ::: List(TI) -} +def move(from: Char, to: Char) = + println(s"Move disc from $from to $to!") -def comp(ts: List[Token], stk: List[Int]) : Int = (ts, stk) match { - case (Nil, st) => st.head - case (T(n)::rest, st) => comp(rest, n::st) - case (PL::rest, n1::n2::st) => comp(rest, n1 + n2::st) - case (TI::rest, n1::n2::st) => comp(rest, n1 * n2::st) -} - -def exp(ts: List[Token], st: List[Exp]) : Exp = (ts, st) match { - case (Nil, st) => st.head - case (T(n)::rest, st) => exp(rest, N(n)::st) - case (PL::rest, n1::n2::st) => exp(rest, Plus(n2, n1)::st) - case (TI::rest, n1::n2::st) => exp(rest, Times(n2, n1)::st) -} - -exp(toks(e2), Nil) - -def proc(s: String) = s match { - case "+" => PL - case "*" => TI - case n => T(n.toInt) -} - - -string(exp("1 2 + 4 * 5 + 3 +".split(" ").toList.map(proc), Nil)) - - - -// Tail recursion -//================ - +def hanoi(n: Int, from: Char, via: Char, to: Char) : Unit = { + if (n == 0) () + else { + hanoi(n - 1, from, to, via) + move(from, to) + hanoi(n - 1, via, from, to) + } +} -def fact(n: Long): Long = - if (n == 0) 1 else n * fact(n - 1) - -def factB(n: BigInt): BigInt = - if (n == 0) 1 else n * factB(n - 1) - -factB(100000) - -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) -println(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 +hanoi(4, 'A', 'B', 'C') @@ -380,8 +106,6 @@ // the first n prefixes of xs // for 1 => include xs - - def moves(xs: List[Int], n: Int) : List[List[Int]] = (xs, n) match { case (Nil, _) => Nil case (xs, 0) => Nil @@ -424,12 +148,12 @@ 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 + val results = children.map(cs => jumps(cs).map(x :: _)).flatten + if (xs.length < x) List(x)::results else results } } -println(jumps(List(5,3,2,5,1,1)).minBy(_.length)) +jumps(List(5,3,2,5,1,1)).minBy(_.length) 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)) @@ -449,37 +173,176 @@ -// Tail Recursion -//================ - - -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 +// 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 -> 10 +*/ + +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 + + +// String interpolations as patterns + +val date = "2019-11-26" +val s"$year-$month-$day" = date + +def parse_date(date: String) : Option[(Int, Int, Int)]= date match { + case s"$year-$month-$day" => Some((day.toInt, month.toInt, year.toInt)) + case s"$day/$month/$year" => Some((day.toInt, month.toInt, year.toInt)) + case s"$day.$month.$year" => Some((day.toInt, month.toInt, year.toInt)) + case _ => None +} +parse_date("2019-11-26") +parse_date("26/11/2019") +parse_date("26.11.2019") + + +// User-defined Datatypes and Pattern Matching +//============================================= + +// trees + +abstract class Exp +case class N(n: Int) extends Exp // for numbers +case class Plus(e1: Exp, e2: Exp) extends Exp +case class Times(e1: Exp, e2: Exp) extends Exp + +def string(e: Exp) : String = e match { + case N(n) => s"$n" + case Plus(e1, e2) => s"(${string(e1)} + ${string(e2)})" + case Times(e1, e2) => s"(${string(e1)} * ${string(e2)})" +} + +val e = Plus(N(9), Times(N(3), N(4))) +println(string(e)) + +def eval(e: Exp) : Int = e match { + case N(n) => n + case Plus(e1, e2) => eval(e1) + eval(e2) + case Times(e1, e2) => eval(e1) * eval(e2) +} + +println(eval(e)) + +def simp(e: Exp) : Exp = e match { + case N(n) => N(n) + case Plus(e1, e2) => (simp(e1), simp(e2)) match { + case (N(0), e2s) => e2s + case (e1s, N(0)) => e1s + case (e1s, e2s) => Plus(e1s, e2s) + } + case Times(e1, e2) => (simp(e1), simp(e2)) match { + case (N(0), _) => N(0) + case (_, N(0)) => N(0) + case (N(1), e2s) => e2s + case (e1s, N(1)) => e1s + case (e1s, e2s) => Times(e1s, e2s) + } +} + + +val e2 = Times(Plus(N(0), N(1)), Plus(N(0), N(9))) +println(string(e2)) +println(string(simp(e2))) + + +// Tokens and Reverse Polish Notation +abstract class Token +case class T(n: Int) extends Token +case object PL extends Token +case object TI extends Token + +def rp(e: Exp) : List[Token] = e match { + case N(n) => List(T(n)) + case Plus(e1, e2) => rp(e1) ::: rp(e2) ::: List(PL) + case Times(e1, e2) => rp(e1) ::: rp(e2) ::: List(TI) +} +println(string(e2)) +println(rp(e2)) + +def comp(ls: List[Token], st: List[Int]) : Int = (ls, st) match { + case (Nil, st) => st.head + case (T(n)::rest, st) => comp(rest, n::st) + case (PL::rest, n1::n2::st) => comp(rest, n1 + n2::st) + case (TI::rest, n1::n2::st) => comp(rest, n1 * n2::st) +} + +comp(rp(e), Nil) + +def proc(s: String) : Token = s match { + case "+" => PL + case "*" => TI + case _ => T(s.toInt) +} + +comp("1 2 + 4 * 5 + 3 +".split(" ").toList.map(proc), Nil) @@ -555,7 +418,7 @@ if (isDone(game)) List(game) else { val cs = candidates(game, emptyPosition(game)) - cs.par.map(c => search(update(game, empty(game), c))).toList.flatten + cs.map(c => search(update(game, empty(game), c))).toList.flatten } } @@ -609,6 +472,45 @@ time_needed(1, search(game2)) + + + +// Tail recursion +//================ + + +def fact(n: Long): Long = + if (n == 0) 1 else n * fact(n - 1) + +def factB(n: BigInt): BigInt = + if (n == 0) 1 else n * factB(n - 1) + +factB(100000) + +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) +println(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 + // tail recursive version that searches // for all solutions