progs/lecture3.scala
changeset 155 371acb50643d
parent 153 4383809c176a
child 158 94b11ac19b41
equal deleted inserted replaced
154:39c6b93718f0 155:371acb50643d
     1 // Scala Lecture 3
     1 // Scala Lecture 3
     2 //=================
     2 //=================
     3 
     3 
     4 // adding two binary strings very, very lazy manner
     4 // Pattern Matching
       
     5 //==================
       
     6 
       
     7 // A powerful tool which is supposed to come to Java in a few years
       
     8 // time (https://www.youtube.com/watch?v=oGll155-vuQ)...Scala already
       
     9 // has it for many years. Other functional languages have it for
       
    10 // decades. I think I would refuse to program in a language that
       
    11 // does not have pattern matching....its is just so elegant. ;o)
       
    12 
       
    13 // The general schema:
       
    14 //
       
    15 //    expression match {
       
    16 //       case pattern1 => expression1
       
    17 //       case pattern2 => expression2
       
    18 //       ...
       
    19 //       case patternN => expressionN
       
    20 //    }
       
    21 
       
    22 
       
    23 // remember
       
    24 val lst = List(None, Some(1), Some(2), None, Some(3)).flatten
       
    25 
       
    26 
       
    27 def my_flatten(xs: List[Option[Int]]): List[Int] = {
       
    28   ...?
       
    29 }
       
    30 
       
    31 
       
    32 
       
    33 
       
    34 
       
    35 def my_flatten(lst: List[Option[Int]]): List[Int] = lst match {
       
    36   case Nil => Nil
       
    37   case None::xs => my_flatten(xs)
       
    38   case Some(n)::xs => n::my_flatten(xs)
       
    39 }
       
    40 
       
    41 
       
    42 // another example including a catch-all pattern
       
    43 def get_me_a_string(n: Int): String = n match {
       
    44   case 0 => "zero"
       
    45   case 1 => "one"
       
    46   case 2 => "two"
       
    47   case _ => "many"
       
    48 }
       
    49 
       
    50 get_me_a_string(0)
       
    51 
       
    52 // you can also have cases combined
       
    53 def season(month: String) = month match {
       
    54   case "March" | "April" | "May" => "It's spring"
       
    55   case "June" | "July" | "August" => "It's summer"
       
    56   case "September" | "October" | "November" => "It's autumn"
       
    57   case "December" | "January" | "February" => "It's winter"
       
    58 }
       
    59  
       
    60 println(season("November"))
       
    61 
       
    62 // What happens if no case matches?
       
    63 
       
    64 println(season("foobar"))
       
    65 
       
    66 
       
    67 // Collatz function on binary strings
       
    68 
       
    69 // adding two binary strings in a very, very lazy manner
     5 
    70 
     6 def badd(s1: String, s2: String) : String = 
    71 def badd(s1: String, s2: String) : String = 
     7   (BigInt(s1, 2) + BigInt(s2, 2)).toString(2)
    72   (BigInt(s1, 2) + BigInt(s2, 2)).toString(2)
     8 
    73 
     9 
    74 
    19 bcollatz(9.toBinaryString)
    84 bcollatz(9.toBinaryString)
    20 bcollatz(837799.toBinaryString)
    85 bcollatz(837799.toBinaryString)
    21 bcollatz(100000000000000000L.toBinaryString)
    86 bcollatz(100000000000000000L.toBinaryString)
    22 bcollatz(BigInt("1000000000000000000000000000000000000000000000000000000000000000000000000000").toString(2))
    87 bcollatz(BigInt("1000000000000000000000000000000000000000000000000000000000000000000000000000").toString(2))
    23 
    88 
    24 def conv(c: Char) : Int = c match {
    89 
    25   case '0' => 0
    90 
    26   case '1' => 1
    91 
    27 }
    92 // User-defined Datatypes
    28 
    93 //========================
    29 def badds(s1: String, s2: String, carry: Int) : String = (s1, s2, carry) match {
    94 
    30   case ("", "", 1) => "1"
    95 abstract class Colour
    31   case ("", "", 0) => ""
    96 case class Red() extends Colour 
    32   case (cs1, cs2, carry) => (conv(cs1.last) + conv(cs2.last) + carry) match {
    97 case class Green() extends Colour 
    33     case 3 => badds(cs1.dropRight(1), cs2.dropRight(1), 1) + '1'
    98 case class Blue() extends Colour
    34     case 2 => badds(cs1.dropRight(1), cs2.dropRight(1), 1) + '0'
    99 
    35     case 1 => badds(cs1.dropRight(1), cs2.dropRight(1), 0) + '1'
   100 def fav_colour(c: Colour) : Boolean = c match {
    36     case 0 => badds(cs1.dropRight(1), cs2.dropRight(1), 0) + '0'
   101   case Red()   => false
    37   }
   102   case Green() => true
    38 } 
   103   case Blue()  => false 
    39 
   104 }
    40 def bcollatz2(s: String) : Long = (s.dropRight(1), s.last) match {
   105 
    41   case ("", '1') => 1                                          // we reached 1
   106 
    42   case (rest, '0') => 1 + bcollatz2(rest)                      // even number => divide by two
   107 // actually colors can be written with "object",
    43   case (rest, '1') => 1 + bcollatz2(badds(s + '1', '0' + s, 0))   // odd number => s + '1' is 2 * s + 1
   108 // because they do not take any arguments
    44                                                                   //         add another s gives 3 * s + 1  
       
    45 } 
       
    46 
       
    47 bcollatz2(9.toBinaryString)
       
    48 bcollatz2(837799.toBinaryString)
       
    49 bcollatz2(100000000000000000L.toBinaryString)
       
    50 bcollatz2(BigInt("1000000000000000000000000000000000000000000000000000000000000000000000000000").toString(2))
       
    51 
   109 
    52 
   110 
    53 
   111 
    54 // Roman Numerals
   112 // Roman Numerals
    55 abstract class RomanDigit 
   113 abstract class RomanDigit 
    86 RomanNumeral2Int(List(I,X))             // 9
   144 RomanNumeral2Int(List(I,X))             // 9
    87 RomanNumeral2Int(List(M,C,M,L,X,X,I,X)) // 1979
   145 RomanNumeral2Int(List(M,C,M,L,X,X,I,X)) // 1979
    88 RomanNumeral2Int(List(M,M,X,V,I,I))     // 2017
   146 RomanNumeral2Int(List(M,M,X,V,I,I))     // 2017
    89 
   147 
    90 
   148 
       
   149 
       
   150 // another example
       
   151 //=================
       
   152 
       
   153 // Once upon a time, in a complete fictional country there were persons...
       
   154 
       
   155 abstract class Person
       
   156 case class King() extends Person
       
   157 case class Peer(deg: String, terr: String, succ: Int) extends Person
       
   158 case class Knight(name: String) extends Person
       
   159 case class Peasant(name: String) extends Person
       
   160 
       
   161 
       
   162 def title(p: Person): String = p match {
       
   163   case King() => "His Majesty the King"
       
   164   case Peer(deg, terr, _) => s"The ${deg} of ${terr}"
       
   165   case Knight(name) => s"Sir ${name}"
       
   166   case Peasant(name) => name
       
   167 }
       
   168 
       
   169 
       
   170 def superior(p1: Person, p2: Person): Boolean = (p1, p2) match {
       
   171   case (King(), _) => true
       
   172   case (Peer(_,_,_), Knight(_)) => true
       
   173   case (Peer(_,_,_), Peasant(_)) => true
       
   174   case (Peer(_,_,_), Clown()) => true
       
   175   case (Knight(_), Peasant(_)) => true
       
   176   case (Knight(_), Clown()) => true
       
   177   case (Clown(), Peasant(_)) => true
       
   178   case _ => false
       
   179 }
       
   180 
       
   181 val people = List(Knight("David"), 
       
   182                   Peer("Duke", "Norfolk", 84), 
       
   183                   Peasant("Christian"), 
       
   184                   King(), 
       
   185                   Clown())
       
   186 
       
   187 println(people.sortWith(superior(_, _)).mkString(", "))
       
   188 
       
   189 
       
   190 
       
   191 
    91 // Tail recursion
   192 // Tail recursion
    92 //================
   193 //================
    93 
   194 
    94 def my_contains(elem: Int, lst: List[Int]): Boolean = lst match {
       
    95   case Nil => false
       
    96   case x::xs => 
       
    97     if (x == elem) true else my_contains(elem, xs)
       
    98 }
       
    99 
       
   100 my_contains(4, List(1,2,3))
       
   101 my_contains(2, List(1,2,3))
       
   102 
       
   103 my_contains(1000000, (1 to 1000000).toList)
       
   104 my_contains(1000001, (1 to 1000000).toList)
       
   105 
       
   106 
       
   107 //factorial V0.1
       
   108 import scala.annotation.tailrec
       
   109 
       
   110 
   195 
   111 def fact(n: Long): Long = 
   196 def fact(n: Long): Long = 
   112   if (n == 0) 1 else n * fact(n - 1)
   197   if (n == 0) 1 else n * fact(n - 1)
   113 
   198 
   114 fact(10000)                        // produces a stackoverflow
   199 fact(10)              //ok
       
   200 fact(10000)           // produces a stackoverflow
       
   201 
       
   202 def factT(n: BigInt, acc: BigInt): BigInt =
       
   203   if (n == 0) acc else factT(n - 1, n * acc)
       
   204 
       
   205 factT(100000, 1)
       
   206 
       
   207 // there is a flag for ensuring a function is tail recursive
       
   208 import scala.annotation.tailrec
   115 
   209 
   116 @tailrec
   210 @tailrec
   117 def factT(n: BigInt, acc: BigInt): BigInt =
   211 def factT(n: BigInt, acc: BigInt): BigInt =
   118   if (n == 0) acc else factT(n - 1, n * acc)
   212   if (n == 0) acc else factT(n - 1, n * acc)
   119 
   213 
   120 
   214 
   121 println(factT(10000, 1))
   215 
   122 
   216 // for tail-recursive functions the Scala compiler
   123 // the functions my_contains and factT are tail-recursive 
       
   124 // you can check this with 
       
   125 
       
   126 import scala.annotation.tailrec
       
   127 
       
   128 // and the annotation @tailrec
       
   129 
       
   130 // for tail-recursive functions the scala compiler
       
   131 // generates loop-like code, which does not need
   217 // generates loop-like code, which does not need
   132 // to allocate stack-space in each recursive
   218 // to allocate stack-space in each recursive
   133 // call; scala can do this only for tail-recursive
   219 // call; Scala can do this only for tail-recursive
   134 // functions
   220 // functions
   135 
   221 
   136 // consider the following "stupid" version of the
   222 
   137 // coin exchange problem: given some coins and a
   223 
   138 // total, what is the change can you get?
   224 // sudoku again
   139 
   225 
   140 val coins = List(4,5,6,8,10,13,19,20,21,24,38,39,40)
   226 val game0 = """.14.6.3..
   141 
   227               |62...4..9
   142 def first_positive[B](lst: List[Int], f: Int => Option[B]): Option[B] = lst match {
   228               |.8..5.6..
       
   229               |.6.2....3
       
   230               |.7..1..5.
       
   231               |5....9.6.
       
   232               |..6.2..3.
       
   233               |1..5...92
       
   234               |..7.9.41.""".stripMargin.replaceAll("\\n", "")
       
   235 
       
   236 type Pos = (Int, Int)
       
   237 val EmptyValue = '.'
       
   238 val MaxValue = 9
       
   239 
       
   240 val allValues = "123456789".toList
       
   241 val indexes = (0 to 8).toList
       
   242 
       
   243 
       
   244 def empty(game: String) = game.indexOf(EmptyValue)
       
   245 def isDone(game: String) = empty(game) == -1 
       
   246 def emptyPosition(game: String) = 
       
   247   (empty(game) % MaxValue, empty(game) / MaxValue)
       
   248 
       
   249 
       
   250 def get_row(game: String, y: Int) = 
       
   251   indexes.map(col => game(y * MaxValue + col))
       
   252 def get_col(game: String, x: Int) = 
       
   253   indexes.map(row => game(x + row * MaxValue))
       
   254 
       
   255 def get_box(game: String, pos: Pos): List[Char] = {
       
   256     def base(p: Int): Int = (p / 3) * 3
       
   257     val x0 = base(pos._1)
       
   258     val y0 = base(pos._2)
       
   259     val ys = (y0 until y0 + 3).toList
       
   260     (x0 until x0 + 3).toList.flatMap(x => ys.map(y => game(x + y * MaxValue)))
       
   261 }
       
   262 
       
   263 // this is not mutable!!
       
   264 def update(game: String, pos: Int, value: Char): String = 
       
   265   game.updated(pos, value)
       
   266 
       
   267 def toAvoid(game: String, pos: Pos): List[Char] = 
       
   268   (get_col(game, pos._1) ++ get_row(game, pos._2) ++ get_box(game, pos))
       
   269 
       
   270 def candidates(game: String, pos: Pos): List[Char] = 
       
   271   allValues.diff(toAvoid(game,pos))
       
   272 
       
   273 //candidates(game0, (0,0))
       
   274 
       
   275 def pretty(game: String): String = 
       
   276   "\n" + (game sliding (MaxValue, MaxValue) mkString "\n")
       
   277 
       
   278 // not tail recursive 
       
   279 def search(game: String): List[String] = {
       
   280   if (isDone(game)) List(game)
       
   281   else {
       
   282     val cs = candidates(game, emptyPosition(game))
       
   283     cs.map(c => search(update(game, empty(game), c))).toList.flatten
       
   284   }
       
   285 }
       
   286 
       
   287 // tail recursive version that searches 
       
   288 // for all solution
       
   289 def searchT(games: List[String], sols: List[String]): List[String] = games match {
       
   290   case Nil => sols
       
   291   case game::rest => {
       
   292     if (isDone(game)) searchT(rest, game::sols)
       
   293     else {
       
   294       val cs = candidates(game, emptyPosition(game))
       
   295       searchT(cs.map(c => update(game, empty(game), c)) ::: rest, sols)
       
   296     }
       
   297   }
       
   298 }
       
   299 
       
   300 // tail recursive version that searches 
       
   301 // for a single solution
       
   302 def search1T(games: List[String]): Option[String] = games match {
   143   case Nil => None
   303   case Nil => None
   144   case x::xs => 
   304   case game::rest => {
   145     if (x <= 0) first_positive(xs, f)
   305     if (isDone(game)) Some(game)
   146     else {
   306     else {
   147       val fx = f(x)
   307       val cs = candidates(game, emptyPosition(game))
   148       if (fx.isDefined) fx else first_positive(xs, f)
   308       search1T(cs.map(c => update(game, empty(game), c)) ::: rest)
       
   309     }
   149   }
   310   }
   150 }
   311 }
   151 
   312 
   152 
   313 // game with multiple solutions
   153 import scala.annotation.tailrec
   314 val game3 = """.8...9743
   154 
   315               |.5...8.1.
   155 def search(total: Int, coins: List[Int], cs: List[Int]): Option[List[Int]] = {
   316               |.1.......
   156   if (total < cs.sum) None 
   317               |8....5...
   157   else if (cs.sum == total) Some(cs) 
   318               |...8.4...
   158   else first_positive(coins, (c: Int) => search(total, coins, c::cs))
   319               |...3....6
   159 }
   320               |.......7.
   160 
   321               |.3.5...8.
   161 search(11, coins, Nil)
   322               |9724...5.""".stripMargin.replaceAll("\\n", "")
   162 search(111, coins, Nil)
   323 
   163 search(111111, coins, Nil)
   324 searchT(List(game3), List()).map(pretty)
   164 
   325 search1T(List(game3)).map(pretty)
   165 val junk_coins = List(4,-2,5,6,8,0,10,13,19,20,-3,21,24,38,39, 40)
       
   166 search(11, junk_coins, Nil)
       
   167 search(111, junk_coins, Nil)
       
   168 
       
   169 
       
   170 import scala.annotation.tailrec
       
   171 
       
   172 @tailrec
       
   173 def searchT(total: Int, coins: List[Int], 
       
   174             acc_cs: List[List[Int]]): Option[List[Int]] = acc_cs match {
       
   175   case Nil => None
       
   176   case x::xs => 
       
   177     if (total < x.sum) searchT(total, coins, xs)
       
   178     else if (x.sum == total) Some(x) 
       
   179     else searchT(total, coins, coins.filter(_ > 0).map(_::x) ::: xs)
       
   180 }
       
   181 
       
   182 val start_acc = coins.filter(_ > 0).map(List(_))
       
   183 searchT(11, junk_coins, start_acc)
       
   184 searchT(111, junk_coins, start_acc)
       
   185 searchT(111111, junk_coins, start_acc)
       
   186 
   326 
   187 // Moral: Whenever a recursive function is resource-critical
   327 // Moral: Whenever a recursive function is resource-critical
   188 // (i.e. works with large recursion depths), then you need to
   328 // (i.e. works with large recursion depths), then you need to
   189 // write it in tail-recursive fashion.
   329 // write it in tail-recursive fashion.
   190 // 
   330 // 
   191 // Unfortuantely, the Scala is because of current limitations in 
   331 // Unfortuantely, Scala because of current limitations in 
   192 // the JVM not as clever as other functional languages. It can 
   332 // the JVM is not as clever as other functional languages. It can 
   193 // only optimise "self-tail calls". This excludes the cases of 
   333 // only optimise "self-tail calls". This excludes the cases of 
   194 // multiple functions making tail calls to each other. Well,
   334 // multiple functions making tail calls to each other. Well,
   195 // nothing is perfect. 
   335 // nothing is perfect. 
   196 
   336 
   197 
   337 
   228 
   368 
   229 // Remember?
   369 // Remember?
   230 def first[A, B](xs: List[A], f: A => Option[B]): Option[B] = ...
   370 def first[A, B](xs: List[A], f: A => Option[B]): Option[B] = ...
   231 
   371 
   232 
   372 
   233 // polymorphic classes
   373 
   234 //(trees with some content)
   374 // Cool Stuff
   235 
   375 //============
   236 abstract class Tree[+A]
   376 
   237 case class Node[A](elem: A, left: Tree[A], right: Tree[A]) extends Tree[A]
   377 
   238 case object Leaf extends Tree[Nothing]
   378 
   239 
   379 
   240 val t0 = Node('4', Node('2', Leaf, Leaf), Node('7', Leaf, Leaf))
   380 // Implicits 
   241 
   381 //===========
   242 def insert[A](tr: Tree[A], n: A): Tree[A] = tr match {
   382 //
   243   case Leaf => Node(n, Leaf, Leaf)
   383 // For example adding your own methods to Strings:
   244   case Node(m, left, right) => 
   384 // Imagine you want to increment strings, like
   245     if (n == m) Node(m, left, right) 
   385 //
   246     else if (n < m) Node(m, insert(left, n), right)
   386 //     "HAL".increment
   247     else Node(m, left, insert(right, n))
   387 //
   248 }
   388 // you can avoid ugly fudges, like a MyString, by
   249 
   389 // using implicit conversions.
   250 
   390 
   251 // the A-type needs to be ordered
   391 
   252 
   392 implicit class MyString(s: String) {
   253 abstract class Tree[+A <% Ordered[A]]
   393   def increment = for (c <- s) yield (c + 1).toChar 
   254 case class Node[A <% Ordered[A]](elem: A, left: Tree[A], 
   394 }
   255                                  right: Tree[A]) extends Tree[A]
   395 
   256 case object Leaf extends Tree[Nothing]
   396 "HAL".increment
   257 
   397 
   258 
       
   259 def insert[A <% Ordered[A]](tr: Tree[A], n: A): Tree[A] = tr match {
       
   260   case Leaf => Node(n, Leaf, Leaf)
       
   261   case Node(m, left, right) => 
       
   262     if (n == m) Node(m, left, right) 
       
   263     else if (n < m) Node(m, insert(left, n), right)
       
   264     else Node(m, left, insert(right, n))
       
   265 }
       
   266 
       
   267 
       
   268 val t1 = Node(4, Node(2, Leaf, Leaf), Node(7, Leaf, Leaf))
       
   269 insert(t1, 3)
       
   270 
       
   271 val t2 = Node('b', Node('a', Leaf, Leaf), Node('f', Leaf, Leaf))
       
   272 insert(t2, 'e')
       
   273 
   398 
   274 
   399 
   275 
   400 
   276 // Regular expressions - the power of DSLs in Scala
   401 // Regular expressions - the power of DSLs in Scala
   277 //==================================================
   402 //==================================================
   278 
   403 
   279 
       
   280 abstract class Rexp
   404 abstract class Rexp
   281 case object ZERO extends Rexp
   405 case object ZERO extends Rexp                       // nothing
   282 case object ONE extends Rexp
   406 case object ONE extends Rexp                        // the empty string
   283 case class CHAR(c: Char) extends Rexp
   407 case class CHAR(c: Char) extends Rexp               // a character c
   284 case class ALT(r1: Rexp, r2: Rexp) extends Rexp     // alternative  r1 + r2
   408 case class ALT(r1: Rexp, r2: Rexp) extends Rexp     // alternative  r1 + r2
   285 case class SEQ(r1: Rexp, r2: Rexp) extends Rexp     // sequence     r1 r2  
   409 case class SEQ(r1: Rexp, r2: Rexp) extends Rexp     // sequence     r1 o r2  
   286 case class STAR(r: Rexp) extends Rexp               // star         r*
   410 case class STAR(r: Rexp) extends Rexp               // star         r*
   287 
   411 
   288 
   412 
   289 // (ab)*
   413 // (ab)*
   290 val r0 = STAR(SEQ(CHAR('a'), CHAR('b')))
   414 val r0 = STAR(SEQ(CHAR('a'), CHAR('b')))
   324 val digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
   448 val digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
   325 val sign = "+" | "-" | ""
   449 val sign = "+" | "-" | ""
   326 val number = sign ~ digit ~ digit.% 
   450 val number = sign ~ digit ~ digit.% 
   327 
   451 
   328 
   452 
   329 //implement print_re
       
   330 
       
   331 
       
   332 
       
   333 // Lazyness with style
       
   334 //=====================
       
   335 
       
   336 // The concept of lazy evaluation doesn’t really exist in 
       
   337 // non-functional languages, but it is pretty easy to grasp. 
       
   338 // Consider first 
       
   339 
       
   340 def square(x: Int) = x * x
       
   341 
       
   342 square(42 + 8)
       
   343 
       
   344 // this is called strict evaluation
       
   345 
       
   346 
       
   347 def expensiveOperation(n: BigInt): Boolean = expensiveOperation(n + 1) 
       
   348 val a = "foo"
       
   349 val b = "bar"
       
   350 
       
   351 val test = if ((a == b) || expensiveOperation(0)) true else false
       
   352 
       
   353 // this is called lazy evaluation
       
   354 // you delay compuation until it is really 
       
   355 // needed; once calculated though, does not 
       
   356 // need to be re-calculated
       
   357 
       
   358 // a useful example is
       
   359 def time_needed[T](i: Int, code: => T) = {
       
   360   val start = System.nanoTime()
       
   361   for (j <- 1 to i) code
       
   362   val end = System.nanoTime()
       
   363   ((end - start) / i / 1.0e9) + " secs"
       
   364 }
       
   365 
       
   366 
       
   367 // streams (I do not care how many)
       
   368 // primes: 2, 3, 5, 7, 9, 11, 13 ....
       
   369 
       
   370 def generatePrimes (s: Stream[Int]): Stream[Int] =
       
   371   s.head #:: generatePrimes(s.tail filter (_ % s.head != 0))
       
   372 
       
   373 val primes: Stream[Int] = generatePrimes(Stream.from(2))
       
   374 
       
   375 primes.take(10).toList
       
   376 
       
   377 primes.filter(_ > 100).take(2000).toList
       
   378 
       
   379 time_needed(1, primes.filter(_ > 100).take(2000).toList)
       
   380 time_needed(1, primes.filter(_ > 100).take(2000).toList)
       
   381 
       
   382 
       
   383 
       
   384 // streams are useful for implementing search problems ;o)
       
   385 
       
   386 
   453 
   387 
   454 
   388 
   455 
   389 // The End
   456 // The End
   390 //=========
   457 //=========
   395 // reason not to.
   462 // reason not to.
   396 
   463 
   397 // You can be productive on Day 1, but the language is deep.
   464 // You can be productive on Day 1, but the language is deep.
   398 
   465 
   399 // I like best about Scala that it lets me write
   466 // I like best about Scala that it lets me write
   400 // concise, readable code
   467 // concise, readable code.
   401 
   468