progs/lecture3.scala
changeset 366 1c829680503e
parent 364 f1a6fa599d26
child 374 90b267768329
equal deleted inserted replaced
365:fc118ee0fce4 366:1c829680503e
     1 // Scala Lecture 3
     1 // Scala Lecture 3
     2 //=================
     2 //=================
     3 
     3 
     4 // - last week
       
     5 //
       
     6 // option type 
       
     7 // higher-order function
       
     8 
       
     9 
       
    10 def add(x: Int, y: Int) : Int = x + y
       
    11 
       
    12 def plus5(x: Int) : Int = add(5, x)
       
    13 
       
    14 plus5(6)
       
    15 
       
    16 def add2(x: Int)(y: Int) : Int = x + y
       
    17 
       
    18 def plus3(y: Int) : Int => Int = add2(3)(y)
       
    19 
       
    20 plus3(9)
       
    21 
       
    22 List(1,2,3,4,5).map(add2(3))
       
    23 List(1,2,3,4,5).map(add(3, _))
       
    24 
       
    25 type Pos = (Int, Int)
       
    26 
       
    27 def test(p: Pos) = {
       
    28   if (p._1 < 5 && p._2 < 5) {
       
    29     Some(p)
       
    30   }
       
    31 }
       
    32 
       
    33 val l = List((1,2), (5,3), (2,5), (1,3))
       
    34 
       
    35 l.map(test).flatten
       
    36 
     4 
    37 // naive quicksort with "On" function
     5 // naive quicksort with "On" function
    38 
     6 
    39 def sortOn(f: Int => Int, xs: List[Int]) : List[Int] = {
     7 def sortOn(f: Int => Int, xs: List[Int]) : List[Int] = {
    40   if (xs.size < 2) xs
     8   if (xs.size < 2) xs
    47 
    15 
    48 sortOn(identity, List(99,99,99,98,10,-3,2)) 
    16 sortOn(identity, List(99,99,99,98,10,-3,2)) 
    49 sortOn(n => - n, List(99,99,99,98,10,-3,2))
    17 sortOn(n => - n, List(99,99,99,98,10,-3,2))
    50 
    18 
    51 
    19 
    52 
       
    53 
       
    54 // Recursion Again ;o)
    20 // Recursion Again ;o)
    55 //====================
    21 //====================
    56 
    22 
    57 
    23 
    58 // A Web Crawler / Email Harvester
    24 // another well-known example: Towers of Hanoi
    59 //=================================
    25 //=============================================
    60 //
       
    61 // the idea is to look for links using the
       
    62 // regular expression "https?://[^"]*" and for
       
    63 // email addresses using yet another regex.
       
    64 
       
    65 import io.Source
       
    66 import scala.util._
       
    67 
       
    68 // gets the first 10K of a web-page
       
    69 def get_page(url: String) : String = {
       
    70   Try(Source.fromURL(url)("ISO-8859-1").take(10000).mkString).
       
    71     getOrElse { println(s" Problem with: $url"); ""}
       
    72 }
       
    73 
       
    74 // regex for URLs and emails
       
    75 val http_pattern = """"https?://[^"]*"""".r
       
    76 val email_pattern = """([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})""".r
       
    77 
       
    78 //  val s = "foo bla christian@kcl.ac.uk 1234567"
       
    79 //  email_pattern.findAllIn(s).toList
       
    80 
       
    81 // drops the first and last character from a string
       
    82 def unquote(s: String) = s.drop(1).dropRight(1)
       
    83 
       
    84 def get_all_URLs(page: String): Set[String] = 
       
    85   http_pattern.findAllIn(page).map(unquote).toSet
       
    86 
       
    87 // a naive version of crawl - searches until a given depth,
       
    88 // visits pages potentially more than once
       
    89 def crawl(url: String, n: Int) : Unit = {
       
    90   if (n == 0) ()
       
    91   else {
       
    92     println(s"  Visiting: $n $url")
       
    93     val page = get_page(url)
       
    94     for (u <- get_all_URLs(page)) crawl(u, n - 1)
       
    95   }
       
    96 }
       
    97 
       
    98 // some starting URLs for the crawler
       
    99 val startURL = """https://nms.kcl.ac.uk/christian.urban/"""
       
   100 
       
   101 crawl(startURL, 2)
       
   102 
       
   103 for (x <- List(1,2,3,4,5,6)) println(x)
       
   104 
       
   105 // a primitive email harvester
       
   106 def emails(url: String, n: Int) : Set[String] = {
       
   107   if (n == 0) Set()
       
   108   else {
       
   109     println(s"  Visiting: $n $url")
       
   110     val page = get_page(url)
       
   111     val new_emails = email_pattern.findAllIn(page).toSet
       
   112     new_emails ++ (for (u <- get_all_URLs(page).par) yield emails(u, n - 1)).flatten
       
   113   }
       
   114 }
       
   115 
       
   116 emails(startURL, 3)
       
   117 
       
   118 
       
   119 // if we want to explore the internet "deeper", then we
       
   120 // first have to parallelise the request of webpages:
       
   121 //
       
   122 // scala -cp scala-parallel-collections_2.13-0.2.0.jar 
       
   123 // import scala.collection.parallel.CollectionConverters._
       
   124 
       
   125 
       
   126 
       
   127 // another well-known example
       
   128 //============================
       
   129 
    26 
   130 def move(from: Char, to: Char) =
    27 def move(from: Char, to: Char) =
   131   println(s"Move disc from $from to $to!")
    28   println(s"Move disc from $from to $to!")
   132 
    29 
   133 def hanoi(n: Int, from: Char, via: Char, to: Char) : Unit = {
    30 def hanoi(n: Int, from: Char, via: Char, to: Char) : Unit = {
   141 
    38 
   142 hanoi(4, 'A', 'B', 'C')
    39 hanoi(4, 'A', 'B', 'C')
   143 
    40 
   144 
    41 
   145 
    42 
   146 // Jumping Towers
       
   147 //================
       
   148 
       
   149 
       
   150 // the first n prefixes of xs
       
   151 // for 1 => include xs
       
   152 
       
   153 def moves(xs: List[Int], n: Int) : List[List[Int]] = (xs, n) match {
       
   154   case (Nil, _) => Nil
       
   155   case (_, 0) => Nil
       
   156   case (y::ys, n) => xs :: moves(ys, n - 1)
       
   157 }
       
   158 
       
   159 
       
   160 moves(List(5,1,0), 1)
       
   161 moves(List(5,1,0), 2)
       
   162 moves(List(5,1,0), 5)
       
   163 
       
   164 // checks whether a jump tour exists at all
       
   165 
       
   166 def search(xs: List[Int]) : Boolean = xs match {
       
   167   case Nil => true
       
   168   case x::xs =>
       
   169     if (xs.length < x) true 
       
   170     else moves(xs, x).exists(search(_))
       
   171 }
       
   172 
       
   173 
       
   174 search(List(5,3,2,5,1,1))
       
   175 search(List(3,5,1,0,0,0,1))
       
   176 search(List(3,5,1,0,0,0,0,1))
       
   177 search(List(3,5,1,0,0,0,1,1))
       
   178 search(List(3,5,1))
       
   179 search(List(5,1,1))
       
   180 search(Nil)
       
   181 search(List(1))
       
   182 search(List(5,1,1))
       
   183 search(List(3,5,1,0,0,0,0,0,0,0,0,1))
       
   184 
       
   185 // generates *all* jump tours
       
   186 //    if we are only interested in the shortest one, we could
       
   187 //    shortcircut the calculation and only return List(x) in
       
   188 //    case where xs.length < x, because no tour can be shorter
       
   189 //    than 1
       
   190 // 
       
   191 
       
   192 def jumps(xs: List[Int]) : List[List[Int]] = xs match {
       
   193   case Nil => Nil
       
   194   case x::xs => {
       
   195     val children = moves(xs, x)
       
   196     val results = children.map(cs => jumps(cs).map(x :: _)).flatten
       
   197     if (xs.length < x) List(x)::results else results
       
   198   }
       
   199 }
       
   200 
       
   201 jumps(List(5,3,2,5,1,1)).minBy(_.length)
       
   202 jumps(List(3,5,1,2,1,2,1))
       
   203 jumps(List(3,5,1,2,3,4,1))
       
   204 jumps(List(3,5,1,0,0,0,1))
       
   205 jumps(List(3,5,1))
       
   206 jumps(List(5,1,1))
       
   207 jumps(Nil)
       
   208 jumps(List(1))
       
   209 jumps(List(5,1,2))
       
   210 moves(List(1,2), 5)
       
   211 jumps(List(1,5,1,2))
       
   212 jumps(List(3,5,1,0,0,0,0,0,0,0,0,1))
       
   213 
       
   214 jumps(List(5,3,2,5,1,1)).minBy(_.length)
       
   215 jumps(List(1,3,5,8,9,2,6,7,6,8,9)).minBy(_.length)
       
   216 jumps(List(1,3,6,1,0,9)).minBy(_.length)
       
   217 jumps(List(2,3,1,1,2,4,2,0,1,1)).minBy(_.length)
       
   218 
       
   219 
       
   220 
       
   221 
       
   222 
       
   223 
       
   224 // User-defined Datatypes
    43 // User-defined Datatypes
   225 //========================
    44 //========================
   226 
    45 
   227 abstract class Tree
    46 abstract class Tree
   228 case class Leaf(x: Int) extends Tree
    47 case class Leaf(x: Int) extends Tree
   229 case class Node(s: String, left: Tree, right: Tree) extends Tree 
    48 case class Node(s: String, left: Tree, right: Tree) extends Tree 
   230 
    49 
   231 List(Leaf(20), Node("foo", Leaf(1), Leaf(2)))
    50 val lf = Leaf(20)
   232 
    51 val tr = Node("foo", Leaf(10), Leaf(23))
   233 sealed abstract class Colour
    52 
       
    53 val lst : List[Tree] = List(lf, tr)
       
    54 
       
    55 
       
    56 abstract class Colour
   234 case object Red extends Colour 
    57 case object Red extends Colour 
   235 case object Green extends Colour 
    58 case object Green extends Colour 
   236 case object Blue extends Colour
    59 case object Blue extends Colour
   237 case object Yellow extends Colour
    60 case object Yellow extends Colour
   238 
    61 
   240 def fav_colour(c: Colour) : Boolean = c match {
    63 def fav_colour(c: Colour) : Boolean = c match {
   241   case Green => true
    64   case Green => true
   242   case _  => false 
    65   case _  => false 
   243 }
    66 }
   244 
    67 
   245 fav_colour(Green)
    68 fav_colour(Blue)
       
    69 
   246 
    70 
   247 // ... a tiny bit more useful: Roman Numerals
    71 // ... a tiny bit more useful: Roman Numerals
   248 
    72 
   249 sealed abstract class RomanDigit 
    73 sealed abstract class RomanDigit 
   250 case object I extends RomanDigit 
    74 case object I extends RomanDigit 
   255 case object D extends RomanDigit 
    79 case object D extends RomanDigit 
   256 case object M extends RomanDigit 
    80 case object M extends RomanDigit 
   257 
    81 
   258 type RomanNumeral = List[RomanDigit] 
    82 type RomanNumeral = List[RomanDigit] 
   259 
    83 
   260 List(X,I,M,D)
    84 List(X,I,M,A)
   261 
    85 
   262 /*
    86 /*
   263 I    -> 1
    87 I    -> 1
   264 II   -> 2
    88 II   -> 2
   265 III  -> 3
    89 III  -> 3
   295 RomanNumeral2Int(List(I,X))             // 9
   119 RomanNumeral2Int(List(I,X))             // 9
   296 RomanNumeral2Int(List(M,C,M,L,X,X,I,X)) // 1979
   120 RomanNumeral2Int(List(M,C,M,L,X,X,I,X)) // 1979
   297 RomanNumeral2Int(List(M,M,X,V,I,I))     // 2017
   121 RomanNumeral2Int(List(M,M,X,V,I,I))     // 2017
   298 
   122 
   299 
   123 
       
   124 // expressions (essentially trees)
       
   125 
       
   126 abstract class Exp
       
   127 case class N(n: Int) extends Exp                  // for numbers
       
   128 case class Plus(e1: Exp, e2: Exp) extends Exp
       
   129 case class Times(e1: Exp, e2: Exp) extends Exp
       
   130 
       
   131 def string(e: Exp) : String = e match {
       
   132   case N(n) => s"$n"
       
   133   case Plus(e1, e2) => s"(${string(e1)} + ${string(e2)})" 
       
   134   case Times(e1, e2) => s"(${string(e1)} * ${string(e2)})"
       
   135 }
       
   136 
       
   137 val e = Plus(N(9), Times(N(3), N(4)))
       
   138 e.toString
       
   139 println(string(e))
       
   140 
       
   141 def eval(e: Exp) : Int = e match {
       
   142   case N(n) => n
       
   143   case Plus(e1, e2) => eval(e1) + eval(e2) 
       
   144   case Times(e1, e2) => eval(e1) * eval(e2) 
       
   145 }
       
   146 
       
   147 println(eval(e))
       
   148 
       
   149 // simplification rules:
       
   150 // e + 0, 0 + e => e 
       
   151 // e * 0, 0 * e => 0
       
   152 // e * 1, 1 * e => e
       
   153 //
       
   154 // (....9 ....)
       
   155 
       
   156 def simp(e: Exp) : Exp = e match {
       
   157   case N(n) => N(n)
       
   158   case Plus(e1, e2) => (simp(e1), simp(e2)) match {
       
   159     case (N(0), e2s) => e2s
       
   160     case (e1s, N(0)) => e1s
       
   161     case (e1s, e2s) => Plus(e1s, e2s)
       
   162   }  
       
   163   case Times(e1, e2) => (simp(e1), simp(e2)) match {
       
   164     case (N(0), _) => N(0)
       
   165     case (_, N(0)) => N(0)
       
   166     case (N(1), e2s) => e2s
       
   167     case (e1s, N(1)) => e1s
       
   168     case (e1s, e2s) => Times(e1s, e2s)
       
   169   }  
       
   170 }
       
   171 
       
   172 
       
   173 val e2 = Times(Plus(N(0), N(1)), Plus(N(0), N(9)))
       
   174 println(string(e2))
       
   175 println(string(simp(e2)))
       
   176 
       
   177 
       
   178 
   300 // String interpolations as patterns
   179 // String interpolations as patterns
   301 
   180 
   302 val date = "2019-11-26"
   181 val date = "2019-11-26"
   303 val s"$year-$month-$day" = date
   182 val s"$year-$month-$day" = date
   304 
   183 
   312 parse_date("2019-11-26")
   191 parse_date("2019-11-26")
   313 parse_date("26/11/2019")
   192 parse_date("26/11/2019")
   314 parse_date("26.11.2019")
   193 parse_date("26.11.2019")
   315 
   194 
   316 
   195 
   317 // User-defined Datatypes and Pattern Matching
       
   318 //=============================================
       
   319 
       
   320 
   196 
   321 
   197 
   322 
   198 
   323 // Tail recursion
   199 // Tail recursion
   324 //================
   200 //================
   353 // for tail-recursive functions the Scala compiler
   229 // for tail-recursive functions the Scala compiler
   354 // generates loop-like code, which does not need
   230 // generates loop-like code, which does not need
   355 // to allocate stack-space in each recursive
   231 // to allocate stack-space in each recursive
   356 // call; Scala can do this only for tail-recursive
   232 // call; Scala can do this only for tail-recursive
   357 // functions
   233 // functions
       
   234 
       
   235 
       
   236 
       
   237 
       
   238 // Sudoku
       
   239 //========
       
   240 
       
   241 
       
   242 type Pos = (Int, Int)
       
   243 val emptyValue = '.'
       
   244 val maxValue = 9
       
   245 
       
   246 val allValues = "123456789".toList
       
   247 val indexes = (0 to 8).toList
       
   248 
       
   249 
       
   250 def empty(game: String) = game.indexOf(emptyValue)
       
   251 def isDone(game: String) = empty(game) == -1 
       
   252 def emptyPosition(game: String) : Pos = 
       
   253   (empty(game) % maxValue, empty(game) / maxValue)
       
   254 
       
   255 
       
   256 def get_row(game: String, y: Int) = indexes.map(col => game(y * maxValue + col))
       
   257 def get_col(game: String, x: Int) = indexes.map(row => game(x + row * maxValue))
       
   258 
       
   259 def get_box(game: String, pos: Pos): List[Char] = {
       
   260     def base(p: Int): Int = (p / 3) * 3
       
   261     val x0 = base(pos._1)
       
   262     val y0 = base(pos._2)
       
   263     for (x <- (x0 until x0 + 3).toList;
       
   264          y <- (y0 until y0 + 3).toList) yield game(x + y * maxValue)
       
   265 }         
       
   266 
       
   267 
       
   268 def update(game: String, pos: Int, value: Char): String = 
       
   269   game.updated(pos, value)
       
   270 
       
   271 def toAvoid(game: String, pos: Pos): List[Char] = 
       
   272   (get_col(game, pos._1) ++ get_row(game, pos._2) ++ get_box(game, pos))
       
   273 
       
   274 def candidates(game: String, pos: Pos): List[Char] = 
       
   275   allValues.diff(toAvoid(game, pos))
       
   276 
       
   277 def search(game: String): List[String] = {
       
   278   if (isDone(game)) List(game)
       
   279   else 
       
   280     candidates(game, emptyPosition(game)).par.
       
   281       map(c => search(update(game, empty(game), c))).toList.flatten
       
   282 }
       
   283 
       
   284 def search1T(games: List[String]): Option[String] = games match {
       
   285   case Nil => None
       
   286   case game::rest => {
       
   287     if (isDone(game)) Some(game)
       
   288     else {
       
   289       val cs = candidates(game, emptyPosition(game))
       
   290       search1T(cs.map(c => update(game, empty(game), c)) ::: rest)
       
   291     }
       
   292   }
       
   293 }
       
   294 
       
   295 def pretty(game: String): String = 
       
   296   "\n" + (game.sliding(maxValue, maxValue).mkString(",\n"))
       
   297 
   358 
   298 
   359 // tail recursive version that searches 
   299 // tail recursive version that searches 
   360 // for all solutions
   300 // for all solutions
   361 
   301 
   362 def searchT(games: List[String], sols: List[String]): List[String] = games match {
   302 def searchT(games: List[String], sols: List[String]): List[String] = games match {