| author | Christian Urban <urbanc@in.tum.de> | 
| Fri, 24 Nov 2017 09:10:53 +0000 | |
| changeset 157 | 2bc526c28a3f | 
| parent 155 | eccf17f56922 | 
| child 158 | f60e0908f80b | 
| permissions | -rw-r--r-- | 
| 67 | 1 | // Scala Lecture 3 | 
| 2 | //================= | |
| 3 | ||
| 155 | 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 | |
| 152 | 70 | |
| 71 | def badd(s1: String, s2: String) : String = | |
| 72 | (BigInt(s1, 2) + BigInt(s2, 2)).toString(2) | |
| 73 | ||
| 74 | ||
| 75 | // collatz function on binary numbers | |
| 76 | ||
| 77 | def bcollatz(s: String) : Long = (s.dropRight(1), s.last) match {
 | |
| 78 |   case ("", '1') => 1                                  // we reached 1
 | |
| 79 | case (rest, '0') => 1 + bcollatz(rest) // even number => divide by two | |
| 80 | case (rest, '1') => 1 + bcollatz(badd(s + '1', s)) // odd number => s + '1' is 2 * s + 1 | |
| 81 | // add another s gives 3 * s + 1 | |
| 82 | } | |
| 83 | ||
| 84 | bcollatz(9.toBinaryString) | |
| 85 | bcollatz(837799.toBinaryString) | |
| 86 | bcollatz(100000000000000000L.toBinaryString) | |
| 87 | bcollatz(BigInt("1000000000000000000000000000000000000000000000000000000000000000000000000000").toString(2))
 | |
| 88 | ||
| 155 | 89 | |
| 90 | ||
| 91 | ||
| 92 | // User-defined Datatypes | |
| 93 | //======================== | |
| 94 | ||
| 95 | abstract class Colour | |
| 96 | case class Red() extends Colour | |
| 97 | case class Green() extends Colour | |
| 98 | case class Blue() extends Colour | |
| 99 | ||
| 100 | def fav_colour(c: Colour) : Boolean = c match {
 | |
| 101 | case Red() => false | |
| 102 | case Green() => true | |
| 103 | case Blue() => false | |
| 152 | 104 | } | 
| 105 | ||
| 106 | ||
| 155 | 107 | // actually colors can be written with "object", | 
| 108 | // because they do not take any arguments | |
| 152 | 109 | |
| 110 | ||
| 67 | 111 | |
| 153 | 112 | // Roman Numerals | 
| 113 | abstract class RomanDigit | |
| 114 | case object I extends RomanDigit | |
| 115 | case object V extends RomanDigit | |
| 116 | case object X extends RomanDigit | |
| 117 | case object L extends RomanDigit | |
| 118 | case object C extends RomanDigit | |
| 119 | case object D extends RomanDigit | |
| 120 | case object M extends RomanDigit | |
| 121 | ||
| 122 | type RomanNumeral = List[RomanDigit] | |
| 67 | 123 | |
| 153 | 124 | def RomanNumeral2Int(rs: RomanNumeral): Int = rs match { 
 | 
| 125 | case Nil => 0 | |
| 126 | case M::r => 1000 + RomanNumeral2Int(r) | |
| 127 | case C::M::r => 900 + RomanNumeral2Int(r) | |
| 128 | case D::r => 500 + RomanNumeral2Int(r) | |
| 129 | case C::D::r => 400 + RomanNumeral2Int(r) | |
| 130 | case C::r => 100 + RomanNumeral2Int(r) | |
| 131 | case X::C::r => 90 + RomanNumeral2Int(r) | |
| 132 | case L::r => 50 + RomanNumeral2Int(r) | |
| 133 | case X::L::r => 40 + RomanNumeral2Int(r) | |
| 134 | case X::r => 10 + RomanNumeral2Int(r) | |
| 135 | case I::X::r => 9 + RomanNumeral2Int(r) | |
| 136 | case V::r => 5 + RomanNumeral2Int(r) | |
| 137 | case I::V::r => 4 + RomanNumeral2Int(r) | |
| 138 | case I::r => 1 + RomanNumeral2Int(r) | |
| 67 | 139 | } | 
| 140 | ||
| 153 | 141 | RomanNumeral2Int(List(I,I,I,I)) // 4 (invalid roman number) | 
| 142 | RomanNumeral2Int(List(I,V)) // 4 | |
| 143 | RomanNumeral2Int(List(V,I)) // 6 | |
| 144 | RomanNumeral2Int(List(I,X)) // 9 | |
| 145 | RomanNumeral2Int(List(M,C,M,L,X,X,I,X)) // 1979 | |
| 146 | RomanNumeral2Int(List(M,M,X,V,I,I)) // 2017 | |
| 67 | 147 | |
| 148 | ||
| 155 | 149 | |
| 150 | // another example | |
| 151 | //================= | |
| 152 | ||
| 153 | // Once upon a time, in a complete fictional country there were persons... | |
| 67 | 154 | |
| 155 | 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 | |
| 67 | 167 | } | 
| 168 | ||
| 169 | ||
| 155 | 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(", "))
 | |
| 67 | 188 | |
| 189 | ||
| 155 | 190 | |
| 191 | ||
| 192 | // Tail recursion | |
| 193 | //================ | |
| 72 | 194 | |
| 67 | 195 | |
| 196 | def fact(n: Long): Long = | |
| 197 | if (n == 0) 1 else n * fact(n - 1) | |
| 198 | ||
| 155 | 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 | |
| 67 | 209 | |
| 72 | 210 | @tailrec | 
| 67 | 211 | def factT(n: BigInt, acc: BigInt): BigInt = | 
| 212 | if (n == 0) acc else factT(n - 1, n * acc) | |
| 213 | ||
| 214 | ||
| 215 | ||
| 155 | 216 | // for tail-recursive functions the Scala compiler | 
| 71 | 217 | // generates loop-like code, which does not need | 
| 67 | 218 | // to allocate stack-space in each recursive | 
| 155 | 219 | // call; Scala can do this only for tail-recursive | 
| 67 | 220 | // functions | 
| 221 | ||
| 155 | 222 | |
| 223 | ||
| 224 | // sudoku again | |
| 225 | ||
| 226 | val game0 = """.14.6.3.. | |
| 227 | |62...4..9 | |
| 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", "")
 | |
| 53 | 235 | |
| 155 | 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 | ||
| 67 | 249 | |
| 155 | 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 | |
| 67 | 284 | } | 
| 285 | } | |
| 286 | ||
| 155 | 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 | } | |
| 67 | 298 | } | 
| 299 | ||
| 155 | 300 | // tail recursive version that searches | 
| 301 | // for a single solution | |
| 302 | def search1T(games: List[String]): Option[String] = games match {
 | |
| 67 | 303 | case Nil => None | 
| 155 | 304 |   case game::rest => {
 | 
| 305 | if (isDone(game)) Some(game) | |
| 306 |     else {
 | |
| 307 | val cs = candidates(game, emptyPosition(game)) | |
| 308 | search1T(cs.map(c => update(game, empty(game), c)) ::: rest) | |
| 309 | } | |
| 310 | } | |
| 67 | 311 | } | 
| 312 | ||
| 155 | 313 | // game with multiple solutions | 
| 314 | val game3 = """.8...9743 | |
| 315 | |.5...8.1. | |
| 316 | |.1....... | |
| 317 | |8....5... | |
| 318 | |...8.4... | |
| 319 | |...3....6 | |
| 320 | |.......7. | |
| 321 | |.3.5...8. | |
| 322 |               |9724...5.""".stripMargin.replaceAll("\\n", "")
 | |
| 323 | ||
| 324 | searchT(List(game3), List()).map(pretty) | |
| 325 | search1T(List(game3)).map(pretty) | |
| 67 | 326 | |
| 77 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
73diff
changeset | 327 | // Moral: Whenever a recursive function is resource-critical | 
| 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
73diff
changeset | 328 | // (i.e. works with large recursion depths), then you need to | 
| 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
73diff
changeset | 329 | // write it in tail-recursive fashion. | 
| 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
73diff
changeset | 330 | // | 
| 155 | 331 | // Unfortuantely, Scala because of current limitations in | 
| 332 | // the JVM is not as clever as other functional languages. It can | |
| 77 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
73diff
changeset | 333 | // only optimise "self-tail calls". This excludes the cases of | 
| 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
73diff
changeset | 334 | // multiple functions making tail calls to each other. Well, | 
| 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
73diff
changeset | 335 | // nothing is perfect. | 
| 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
73diff
changeset | 336 | |
| 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
73diff
changeset | 337 | |
| 67 | 338 | |
| 339 | ||
| 71 | 340 | // Polymorphic Types | 
| 341 | //=================== | |
| 342 | ||
| 72 | 343 | // You do not want to write functions like contains, first | 
| 71 | 344 | // and so on for every type of lists. | 
| 345 | ||
| 67 | 346 | |
| 72 | 347 | def length_string_list(lst: List[String]): Int = lst match {
 | 
| 67 | 348 | case Nil => 0 | 
| 72 | 349 | case x::xs => 1 + length_string_list(xs) | 
| 67 | 350 | } | 
| 351 | ||
| 72 | 352 | length_string_list(List("1", "2", "3", "4"))
 | 
| 67 | 353 | |
| 354 | ||
| 355 | def length[A](lst: List[A]): Int = lst match {
 | |
| 356 | case Nil => 0 | |
| 357 | case x::xs => 1 + length(xs) | |
| 358 | } | |
| 359 | ||
| 53 | 360 | |
| 67 | 361 | def map_int_list(lst: List[Int], f: Int => Int): List[Int] = lst match {
 | 
| 362 | case Nil => Nil | |
| 363 | case x::xs => f(x)::map_int_list(xs, f) | |
| 364 | } | |
| 365 | ||
| 366 | map_int_list(List(1, 2, 3, 4), square) | |
| 367 | ||
| 368 | ||
| 369 | // Remember? | |
| 370 | def first[A, B](xs: List[A], f: A => Option[B]): Option[B] = ... | |
| 371 | ||
| 372 | ||
| 373 | ||
| 155 | 374 | // Cool Stuff | 
| 375 | //============ | |
| 72 | 376 | |
| 155 | 377 | |
| 67 | 378 | |
| 379 | ||
| 155 | 380 | // Implicits | 
| 381 | //=========== | |
| 382 | // | |
| 383 | // For example adding your own methods to Strings: | |
| 384 | // Imagine you want to increment strings, like | |
| 385 | // | |
| 386 | // "HAL".increment | |
| 387 | // | |
| 388 | // you can avoid ugly fudges, like a MyString, by | |
| 389 | // using implicit conversions. | |
| 67 | 390 | |
| 391 | ||
| 155 | 392 | implicit class MyString(s: String) {
 | 
| 393 | def increment = for (c <- s) yield (c + 1).toChar | |
| 67 | 394 | } | 
| 395 | ||
| 155 | 396 | "HAL".increment | 
| 67 | 397 | |
| 53 | 398 | |
| 399 | ||
| 400 | ||
| 71 | 401 | // Regular expressions - the power of DSLs in Scala | 
| 402 | //================================================== | |
| 67 | 403 | |
| 404 | abstract class Rexp | |
| 155 | 405 | case object ZERO extends Rexp // nothing | 
| 406 | case object ONE extends Rexp // the empty string | |
| 407 | case class CHAR(c: Char) extends Rexp // a character c | |
| 71 | 408 | case class ALT(r1: Rexp, r2: Rexp) extends Rexp // alternative r1 + r2 | 
| 155 | 409 | case class SEQ(r1: Rexp, r2: Rexp) extends Rexp // sequence r1 o r2 | 
| 71 | 410 | case class STAR(r: Rexp) extends Rexp // star r* | 
| 67 | 411 | |
| 412 | ||
| 413 | // (ab)* | |
| 72 | 414 | val r0 = STAR(SEQ(CHAR('a'), CHAR('b')))
 | 
| 67 | 415 | |
| 416 | ||
| 417 | // some convenience for typing in regular expressions | |
| 418 | import scala.language.implicitConversions | |
| 419 | import scala.language.reflectiveCalls | |
| 420 | ||
| 421 | def charlist2rexp(s: List[Char]): Rexp = s match {
 | |
| 422 | case Nil => ONE | |
| 423 | case c::Nil => CHAR(c) | |
| 424 | case c::s => SEQ(CHAR(c), charlist2rexp(s)) | |
| 425 | } | |
| 426 | implicit def string2rexp(s: String): Rexp = charlist2rexp(s.toList) | |
| 427 | ||
| 428 | ||
| 429 | val r1 = STAR("ab")
 | |
| 430 | val r2 = STAR("")
 | |
| 72 | 431 | val r3 = STAR(ALT("ab", "baa baa black sheep"))
 | 
| 67 | 432 | |
| 433 | implicit def RexpOps (r: Rexp) = new {
 | |
| 434 | def | (s: Rexp) = ALT(r, s) | |
| 435 | def % = STAR(r) | |
| 436 | def ~ (s: Rexp) = SEQ(r, s) | |
| 437 | } | |
| 438 | ||
| 439 | implicit def stringOps (s: String) = new {
 | |
| 440 | def | (r: Rexp) = ALT(s, r) | |
| 441 | def | (r: String) = ALT(s, r) | |
| 442 | def % = STAR(s) | |
| 443 | def ~ (r: Rexp) = SEQ(s, r) | |
| 444 | def ~ (r: String) = SEQ(s, r) | |
| 445 | } | |
| 446 | ||
| 153 | 447 | //example regular expressions | 
| 67 | 448 | val digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | 
| 449 | val sign = "+" | "-" | "" | |
| 450 | val number = sign ~ digit ~ digit.% | |
| 451 | ||
| 452 | ||
| 453 | ||
| 454 | ||
| 455 | ||
| 456 | // The End | |
| 457 | //========= | |
| 458 | ||
| 459 | // A function should do one thing, and only one thing. | |
| 460 | ||
| 461 | // Make your variables immutable, unless there's a good | |
| 462 | // reason not to. | |
| 463 | ||
| 464 | // You can be productive on Day 1, but the language is deep. | |
| 465 | ||
| 68 | 466 | // I like best about Scala that it lets me write | 
| 155 | 467 | // concise, readable code. | 
| 68 | 468 |