| author | Christian Urban <christian dot urban at kcl dot ac dot uk> | 
| Wed, 25 Jan 2017 01:25:17 +0000 | |
| changeset 104 | a7ce74d89085 | 
| parent 95 | 4fa7231fede7 | 
| child 147 | 3e5d8657302f | 
| permissions | -rw-r--r-- | 
| 51 | 1 | // Scala Lecture 2 | 
| 2 | //================= | |
| 3 | ||
| 4 | ||
| 5 | // Option type | |
| 6 | //============= | |
| 53 | 7 | |
| 57 | 8 | //in Java if something unusually happens, you return null; | 
| 53 | 9 | //in Scala you use Option | 
| 10 | // - if the value is present, you use Some(value) | |
| 11 | // - if no value is present, you use None | |
| 12 | ||
| 13 | ||
| 14 | List(7,2,3,4,5,6).find(_ < 4) | |
| 15 | List(5,6,7,8,9).find(_ < 4) | |
| 16 | ||
| 58 | 17 | |
| 18 | // Values in types | |
| 19 | // | |
| 20 | // Boolean: | |
| 21 | // Int: | |
| 22 | // String: | |
| 23 | // | |
| 24 | // Option[String]: | |
| 25 | // | |
| 26 | ||
| 27 | ||
| 51 | 28 | val lst = List(None, Some(1), Some(2), None, Some(3)) | 
| 29 | ||
| 30 | lst.flatten | |
| 53 | 31 | |
| 51 | 32 | Some(1).get | 
| 33 | ||
| 53 | 34 | Some(1).isDefined | 
| 35 | None.isDefined | |
| 36 | ||
| 51 | 37 | val ps = List((3, 0), (3, 2), (4, 2), (2, 0), (1, 0), (1, 1)) | 
| 38 | ||
| 39 | for ((x, y) <- ps) yield {
 | |
| 40 | if (y == 0) None else Some(x / y) | |
| 41 | } | |
| 42 | ||
| 57 | 43 | // getOrElse is for setting a default value | 
| 53 | 44 | |
| 45 | val lst = List(None, Some(1), Some(2), None, Some(3)) | |
| 57 | 46 | for (x <- lst) yield x.getOrElse(0) | 
| 47 | ||
| 48 | ||
| 53 | 49 | |
| 50 | ||
| 58 | 51 | // error handling with Option (no exceptions) | 
| 57 | 52 | // | 
| 53 | // Try(something).getOrElse(what_to_do_in_an_exception) | |
| 54 | // | |
| 53 | 55 | import scala.util._ | 
| 56 | import io.Source | |
| 57 | ||
| 57 | 58 | Source.fromURL("""http://www.inf.kcl.ac.uk/staff/urbanc/""").mkString
 | 
| 53 | 59 | |
| 60 | Try(Source.fromURL("""http://www.inf.kcl.ac.uk/staff/urbanc/""").mkString).getOrElse("")
 | |
| 61 | ||
| 62 | Try(Some(Source.fromURL("""http://www.inf.kcl.ac.uk/staff/urbanc/""").mkString)).getOrElse(None)
 | |
| 63 | ||
| 57 | 64 | // a function that turns strings into numbers | 
| 53 | 65 | Integer.parseInt("12u34")
 | 
| 66 | ||
| 67 | def get_me_an_int(s: String): Option[Int] = | |
| 68 | Try(Some(Integer.parseInt(s))).getOrElse(None) | |
| 69 | ||
| 70 | val lst = List("12345", "foo", "5432", "bar", "x21")
 | |
| 71 | for (x <- lst) yield get_me_an_int(x) | |
| 72 | ||
| 73 | // summing all the numbers | |
| 74 | val sum = lst.flatMap(get_me_an_int(_)).sum | |
| 75 | ||
| 76 | ||
| 77 | // This may not look any better than working with null in Java, but to | |
| 78 | // see the value, you have to put yourself in the shoes of the | |
| 79 | // consumer of the get_me_an_int function, and imagine you didn't | |
| 80 | // write that function. | |
| 81 | // | |
| 82 | // In Java, if you didn't write this function, you'd have to depend on | |
| 57 | 83 | // the Javadoc of the get_me_an_int. If you didn't look at the Javadoc, | 
| 84 | // you might not know that get_me_an_int could return a null, and your | |
| 85 | // code could potentially throw a NullPointerException. | |
| 53 | 86 | |
| 87 | ||
| 88 | ||
| 58 | 89 | // even Scala is not immune to problems like this: | 
| 90 | ||
| 91 | List(5,6,7,8,9).indexOf(7) | |
| 92 | ||
| 93 | ||
| 94 | ||
| 95 | ||
| 96 | ||
| 53 | 97 | // Type abbreviations | 
| 98 | //==================== | |
| 99 | ||
| 100 | // some syntactic convenience | |
| 101 | type Pos = (int, Int) | |
| 102 | ||
| 103 | type Board = List[List[Int]] | |
| 104 | ||
| 105 | ||
| 106 | ||
| 57 | 107 | // Implicits | 
| 108 | //=========== | |
| 109 | // | |
| 110 | // for example adding your own methods to Strings: | |
| 111 | // imagine you want to increment strings, like | |
| 112 | // | |
| 113 | // "HAL".increment | |
| 114 | // | |
| 115 | // you can avoid ugly fudges, like a MyString, by | |
| 116 | // using implicit conversions | |
| 117 | ||
| 118 | ||
| 119 | implicit class MyString(s: String) {
 | |
| 120 | def increment = for (c <- s) yield (c + 1).toChar | |
| 121 | } | |
| 122 | ||
| 123 | "HAL".increment | |
| 124 | ||
| 125 | ||
| 53 | 126 | // No return in Scala | 
| 127 | //==================== | |
| 128 | ||
| 129 | //You should not use "return" in Scala: | |
| 130 | // | |
| 131 | // A return expression, when evaluated, abandons the | |
| 132 | // current computation and returns to the caller of the | |
| 133 | // function in which return appears." | |
| 134 | ||
| 135 | def sq1(x: Int): Int = x * x | |
| 136 | def sq2(x: Int): Int = return x * x | |
| 137 | ||
| 138 | def sumq(ls: List[Int]): Int = {
 | |
| 139 | (for (x <- ls) yield (return x * x)).sum[Int] | |
| 140 | } | |
| 141 | ||
| 142 | sumq(List(1,2,3,4)) | |
| 36 | 143 | |
| 57 | 144 | |
| 53 | 145 | // last expression in a function is the return statement | 
| 146 | def square(x: Int): Int = {
 | |
| 147 |   println(s"The argument is ${x}.")
 | |
| 148 | x * x | |
| 149 | } | |
| 150 | ||
| 55 | 151 | |
| 152 | ||
| 53 | 153 | // Pattern Matching | 
| 154 | //================== | |
| 155 | ||
| 156 | // A powerful tool which is supposed to come to Java in a few years | |
| 157 | // time (https://www.youtube.com/watch?v=oGll155-vuQ)...Scala already | |
| 158 | // has it for many years ;o) | |
| 159 | ||
| 160 | // The general schema: | |
| 161 | // | |
| 162 | //    expression match {
 | |
| 163 | // case pattern1 => expression1 | |
| 164 | // case pattern2 => expression2 | |
| 165 | // ... | |
| 166 | // case patternN => expressionN | |
| 167 | // } | |
| 168 | ||
| 169 | ||
| 170 | // remember | |
| 171 | val lst = List(None, Some(1), Some(2), None, Some(3)).flatten | |
| 172 | ||
| 173 | ||
| 174 | def my_flatten(xs: List[Option[Int]]): List[Int] = {
 | |
| 175 | ... | |
| 176 | } | |
| 177 | ||
| 178 | ||
| 57 | 179 | |
| 180 | ||
| 181 | ||
| 182 | ||
| 53 | 183 | def my_flatten(lst: List[Option[Int]]): List[Int] = lst match {
 | 
| 184 | case Nil => Nil | |
| 185 | case None::xs => my_flatten(xs) | |
| 186 | case Some(n)::xs => n::my_flatten(xs) | |
| 187 | } | |
| 188 | ||
| 189 | ||
| 190 | // another example | |
| 191 | def get_me_a_string(n: Int): String = n match {
 | |
| 192 | case 0 => "zero" | |
| 193 | case 1 => "one" | |
| 194 | case 2 => "two" | |
| 195 | case _ => "many" | |
| 196 | } | |
| 197 | ||
| 57 | 198 | get_me_a_string(0) | 
| 199 | ||
| 76 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 200 | // you can also have cases combined | 
| 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 201 | def season(month: String) = month match {
 | 
| 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 202 | case "March" | "April" | "May" => "It's spring" | 
| 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 203 | case "June" | "July" | "August" => "It's summer" | 
| 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 204 | case "September" | "October" | "November" => "It's autumn" | 
| 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 205 | case "December" | "January" | "February" => "It's winter" | 
| 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 206 | } | 
| 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 207 | |
| 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 208 | println(season("November"))
 | 
| 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 209 | |
| 77 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
76diff
changeset | 210 | // What happens if no case matches? | 
| 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
76diff
changeset | 211 | |
| 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
76diff
changeset | 212 | println(season("foobar"))
 | 
| 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
76diff
changeset | 213 | |
| 55 | 214 | // User-defined Datatypes | 
| 215 | //======================== | |
| 216 | ||
| 217 | abstract class Tree | |
| 218 | case class Node(elem: Int, left: Tree, right: Tree) extends Tree | |
| 219 | case class Leaf() extends Tree | |
| 220 | ||
| 57 | 221 | |
| 55 | 222 | def insert(tr: Tree, n: Int): Tree = tr match {
 | 
| 223 | case Leaf() => Node(n, Leaf(), Leaf()) | |
| 224 | case Node(m, left, right) => | |
| 225 | if (n == m) Node(m, left, right) | |
| 226 | else if (n < m) Node(m, insert(left, n), right) | |
| 227 | else Node(m, left, insert(right, n)) | |
| 228 | } | |
| 229 | ||
| 230 | ||
| 231 | val t1 = Node(4, Node(2, Leaf(), Leaf()), Node(7, Leaf(), Leaf())) | |
| 232 | insert(t1, 3) | |
| 233 | ||
| 61 | 234 | def depth(tr: Tree): Int = tr match {
 | 
| 235 | case Leaf() => 0 | |
| 236 | case Node(_, left, right) => 1 + List(depth(left), depth(right)).max | |
| 237 | } | |
| 238 | ||
| 239 | ||
| 55 | 240 | def balance(tr: Tree): Int = tr match {
 | 
| 241 | case Leaf() => 0 | |
| 61 | 242 | case Node(_, left, right) => depth(left) - depth(right) | 
| 55 | 243 | } | 
| 244 | ||
| 61 | 245 | balance(insert(t1, 3)) | 
| 55 | 246 | |
| 247 | // another example | |
| 248 | ||
| 249 | abstract class Person | |
| 250 | case class King() extends Person | |
| 251 | case class Peer(deg: String, terr: String, succ: Int) extends Person | |
| 252 | case class Knight(name: String) extends Person | |
| 253 | case class Peasant(name: String) extends Person | |
| 254 | case class Clown() extends Person | |
| 255 | ||
| 256 | def title(p: Person): String = p match {
 | |
| 257 | case King() => "His Majesty the King" | |
| 258 |   case Peer(deg, terr, _) => s"The ${deg} of ${terr}"
 | |
| 259 |   case Knight(name) => s"Sir ${name}"
 | |
| 260 | case Peasant(name) => name | |
| 261 | } | |
| 262 | ||
| 263 | def superior(p1: Person, p2: Person): Boolean = (p1, p2) match {
 | |
| 264 | case (King(), _) => true | |
| 265 | case (Peer(_,_,_), Knight(_)) => true | |
| 266 | case (Peer(_,_,_), Peasant(_)) => true | |
| 267 | case (Peer(_,_,_), Clown()) => true | |
| 268 | case (Knight(_), Peasant(_)) => true | |
| 269 | case (Knight(_), Clown()) => true | |
| 270 | case (Clown(), Peasant(_)) => true | |
| 271 | case _ => false | |
| 272 | } | |
| 273 | ||
| 274 | val people = List(Knight("David"), 
 | |
| 57 | 275 |                   Peer("Duke", "Norfolk", 84), 
 | 
| 55 | 276 |                   Peasant("Christian"), 
 | 
| 277 | King(), | |
| 278 | Clown()) | |
| 279 | ||
| 57 | 280 | println(people.sortWith(superior(_, _)).mkString(", "))
 | 
| 281 | ||
| 282 | ||
| 53 | 283 | |
| 284 | // Higher-Order Functions | |
| 285 | //======================== | |
| 286 | ||
| 287 | // functions can take functions as arguments | |
| 288 | ||
| 289 | val lst = (1 to 10).toList | |
| 290 | ||
| 291 | def even(x: Int): Boolean = x % 2 == 0 | |
| 292 | def odd(x: Int): Boolean = x % 2 == 1 | |
| 293 | ||
| 294 | lst.filter(x => even(x)) | |
| 295 | lst.filter(even(_)) | |
| 296 | lst.filter(even) | |
| 297 | ||
| 298 | lst.find(_ > 8) | |
| 299 | ||
| 300 | def square(x: Int): Int = x * x | |
| 301 | ||
| 302 | lst.map(square) | |
| 303 | ||
| 304 | lst.map(square).filter(_ > 4) | |
| 305 | ||
| 55 | 306 | lst.map(square).filter(_ > 4).map(square) | 
| 53 | 307 | |
| 55 | 308 | // in my collatz.scala | 
| 309 | //(1 to bnd).map(i => (collatz(i), i)).maxBy(_._1) | |
| 310 | ||
| 311 | ||
| 57 | 312 | // type of functions, for example f: Int => Int | 
| 313 | ||
| 55 | 314 | def my_map_int(lst: List[Int], f: Int => Int): List[Int] = lst match {
 | 
| 315 | case Nil => Nil | |
| 316 | case x::xs => f(x)::my_map_int(xs, f) | |
| 317 | } | |
| 318 | ||
| 319 | my_map_int(lst, square) | |
| 320 | ||
| 57 | 321 | // other function types | 
| 322 | // | |
| 323 | // f1: (Int, Int) => Int | |
| 324 | // f2: List[String] => Option[Int] | |
| 325 | // ... | |
| 326 | ||
| 55 | 327 | |
| 328 | def sumOf(f: Int => Int, lst: List[Int]): Int = lst match {
 | |
| 329 | case Nil => 0 | |
| 330 | case x::xs => f(x) + sumOf(f, xs) | |
| 331 | } | |
| 332 | ||
| 333 | def sum_squares(lst: List[Int]) = sumOf(square, lst) | |
| 334 | def sum_cubes(lst: List[Int]) = sumOf(x => x * x * x, lst) | |
| 335 | ||
| 336 | sum_squares(lst) | |
| 337 | sum_cubes(lst) | |
| 53 | 338 | |
| 58 | 339 | // lets try it factorial | 
| 340 | def fact(n: Int): Int = ... | |
| 53 | 341 | |
| 57 | 342 | def sum_fact(lst: List[Int]) = sumOf(fact, lst) | 
| 343 | sum_fact(lst) | |
| 56 | 344 | |
| 345 | // Avoid being mutable | |
| 346 | //===================== | |
| 347 | ||
| 348 | // a student showed me... | |
| 349 | import scala.collection.mutable.ListBuffer | |
| 350 | ||
| 95 
4fa7231fede7
added link file
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
77diff
changeset | 351 | |
| 
4fa7231fede7
added link file
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
77diff
changeset | 352 | |
| 56 | 353 | def collatz_max(bnd: Long): (Long, Long) = {
 | 
| 354 | val colNos = ListBuffer[(Long, Long)]() | |
| 355 | for (i <- (1L to bnd).toList) colNos += ((collatz(i), i)) | |
| 356 | colNos.max | |
| 357 | } | |
| 358 | ||
| 359 | def collatz_max(bnd: Long): (Long, Long) = {
 | |
| 360 | (1L to bnd).map((i) => (collatz(i), i)).maxBy(_._1) | |
| 361 | } | |
| 362 | ||
| 363 | //views -> lazy collection | |
| 364 | def collatz_max(bnd: Long): (Long, Long) = {
 | |
| 365 | (1L to bnd).view.map((i) => (collatz(i), i)).maxBy(_._1) | |
| 366 | } | |
| 367 | ||
| 368 | // raises a GC exception | |
| 369 | (1 to 1000000000).filter(_ % 2 == 0).take(10).toList | |
| 370 | // ==> java.lang.OutOfMemoryError: GC overhead limit exceeded | |
| 371 | ||
| 372 | (1 to 1000000000).view.filter(_ % 2 == 0).take(10).toList | |
| 373 | ||
| 57 | 374 | |
| 375 | ||
| 53 | 376 | // Sudoku | 
| 377 | //======== | |
| 378 | ||
| 57 | 379 | // THE POINT OF THIS CODE IS NOT TO BE SUPER | 
| 380 | // EFFICIENT AND FAST, just explaining exhaustive | |
| 381 | // depth-first search | |
| 382 | ||
| 383 | ||
| 55 | 384 | val game0 = """.14.6.3.. | 
| 385 | |62...4..9 | |
| 386 | |.8..5.6.. | |
| 387 | |.6.2....3 | |
| 388 | |.7..1..5. | |
| 389 | |5....9.6. | |
| 390 | |..6.2..3. | |
| 391 | |1..5...92 | |
| 392 |               |..7.9.41.""".stripMargin.replaceAll("\\n", "")
 | |
| 393 | ||
| 394 | type Pos = (Int, Int) | |
| 395 | val EmptyValue = '.' | |
| 396 | val MaxValue = 9 | |
| 397 | ||
| 398 | val allValues = "123456789".toList | |
| 399 | val indexes = (0 to 8).toList | |
| 400 | ||
| 57 | 401 | |
| 402 | ||
| 55 | 403 | |
| 57 | 404 | def empty(game: String) = game.indexOf(EmptyValue) | 
| 405 | def isDone(game: String) = empty(game) == -1 | |
| 406 | def emptyPosition(game: String) = (empty(game) % MaxValue, empty(game) / MaxValue) | |
| 407 | ||
| 55 | 408 | |
| 57 | 409 | def get_row(game: String, y: Int) = indexes.map(col => game(y * MaxValue + col)) | 
| 410 | def get_col(game: String, x: Int) = indexes.map(row => game(x + row * MaxValue)) | |
| 411 | ||
| 412 | def get_box(game: String, pos: Pos): List[Char] = {
 | |
| 55 | 413 | def base(p: Int): Int = (p / 3) * 3 | 
| 414 | val x0 = base(pos._1) | |
| 415 | val y0 = base(pos._2) | |
| 416 | val ys = (y0 until y0 + 3).toList | |
| 417 | (x0 until x0 + 3).toList.flatMap(x => ys.map(y => game(x + y * MaxValue))) | |
| 418 | } | |
| 419 | ||
| 420 | ||
| 57 | 421 | //get_row(game0, 0) | 
| 422 | //get_row(game0, 1) | |
| 423 | //get_box(game0, (3,1)) | |
| 55 | 424 | |
| 425 | def update(game: String, pos: Int, value: Char): String = game.updated(pos, value) | |
| 426 | ||
| 427 | def toAvoid(game: String, pos: Pos): List[Char] = | |
| 57 | 428 | (get_col(game, pos._1) ++ get_row(game, pos._2) ++ get_box(game, pos)) | 
| 55 | 429 | |
| 430 | def candidates(game: String, pos: Pos): List[Char] = allValues diff toAvoid(game,pos) | |
| 431 | ||
| 432 | //candidates(game0, (0,0)) | |
| 433 | ||
| 434 | def pretty(game: String): String = "\n" + (game sliding (MaxValue, MaxValue) mkString "\n") | |
| 435 | ||
| 436 | def search(game: String): List[String] = {
 | |
| 437 | if (isDone(game)) List(game) | |
| 438 | else | |
| 57 | 439 | candidates(game, emptyPosition(game)).map(c => search(update(game, empty(game), c))).toList.flatten | 
| 55 | 440 | } | 
| 441 | ||
| 442 | ||
| 443 | val game1 = """23.915... | |
| 444 | |...2..54. | |
| 445 | |6.7...... | |
| 446 | |..1.....9 | |
| 447 | |89.5.3.17 | |
| 448 | |5.....6.. | |
| 449 | |......9.5 | |
| 450 | |.16..7... | |
| 451 |               |...329..1""".stripMargin.replaceAll("\\n", "")
 | |
| 452 | ||
| 57 | 453 | |
| 55 | 454 | // game that is in the hard category | 
| 455 | val game2 = """8........ | |
| 456 | |..36..... | |
| 457 | |.7..9.2.. | |
| 458 | |.5...7... | |
| 459 | |....457.. | |
| 460 | |...1...3. | |
| 461 | |..1....68 | |
| 462 | |..85...1. | |
| 463 |               |.9....4..""".stripMargin.replaceAll("\\n", "")
 | |
| 464 | ||
| 465 | // game with multiple solutions | |
| 466 | val game3 = """.8...9743 | |
| 467 | |.5...8.1. | |
| 468 | |.1....... | |
| 469 | |8....5... | |
| 470 | |...8.4... | |
| 471 | |...3....6 | |
| 472 | |.......7. | |
| 473 | |.3.5...8. | |
| 474 |               |9724...5.""".stripMargin.replaceAll("\\n", "")
 | |
| 475 | ||
| 57 | 476 | |
| 55 | 477 | search(game0).map(pretty) | 
| 478 | search(game1).map(pretty) | |
| 479 | ||
| 480 | // for measuring time | |
| 481 | def time_needed[T](i: Int, code: => T) = {
 | |
| 482 | val start = System.nanoTime() | |
| 483 | for (j <- 1 to i) code | |
| 484 | val end = System.nanoTime() | |
| 485 | ((end - start) / i / 1.0e9) + " secs" | |
| 486 | } | |
| 487 | ||
| 488 | search(game2).map(pretty) | |
| 57 | 489 | search(game3).distinct.length | 
| 55 | 490 | time_needed(3, search(game2)) | 
| 491 | time_needed(3, search(game3)) | |
| 492 | ||
| 53 | 493 | |
| 494 | ||
| 495 | ||
| 39 | 496 |