| author | Christian Urban <urbanc@in.tum.de> | 
| Fri, 17 Nov 2017 09:24:34 +0000 | |
| changeset 149 | 73ce78e5b91c | 
| parent 148 | fc72f3ab3a57 | 
| child 150 | 19856bd9ec2d | 
| permissions | -rw-r--r-- | 
| 51 | 1 | // Scala Lecture 2 | 
| 2 | //================= | |
| 3 | ||
| 4 | ||
| 148 | 5 | // the pain with overloaded math operations | 
| 147 | 6 | |
| 7 | (100 / 4) | |
| 8 | ||
| 9 | (100 / 3) | |
| 10 | ||
| 11 | (100.toDouble / 3.toDouble) | |
| 12 | ||
| 13 | ||
| 14 | // For-Comprehensions again | |
| 15 | //========================== | |
| 16 | ||
| 17 | def square(n: Int) : Int = n * n | |
| 18 | ||
| 19 | for (n <- (1 to 10).toList) yield {
 | |
| 20 | val res = square(n) | |
| 21 | res | |
| 22 | } | |
| 23 | ||
| 24 | // like in functions, the "last" item inside the yield | |
| 25 | // will be returned; the last item is not necessarily | |
| 26 | // the last line | |
| 27 | ||
| 28 | for (n <- (1 to 10).toList) yield {
 | |
| 29 | if (n % 2 == 0) n | |
| 30 | else square(n) | |
| 31 | } | |
| 32 | ||
| 33 | ||
| 34 | // ...please, please do not write: | |
| 35 | val lst = List(1, 2, 3, 4, 5, 6, 7, 8, 9) | |
| 36 | ||
| 37 | for (i <- (0 until lst.length).toList) yield square(lst(i)) | |
| 38 | ||
| 39 | // this is just so prone to off-by-one errors; | |
| 40 | // write instead | |
| 41 | ||
| 42 | for (e <- lst) yield square(e) | |
| 43 | ||
| 44 | ||
| 45 | //this works for sets as well | |
| 46 | val st = Set(1, 2, 3, 4, 5, 6, 7, 8, 9) | |
| 47 | ||
| 48 | for (e <- st) yield {
 | |
| 49 | if (e < 5) e else square(e) | |
| 50 | } | |
| 51 | ||
| 52 | ||
| 53 | ||
| 54 | // Side-Effects | |
| 55 | //============== | |
| 56 | ||
| 57 | // with only a side-effect (no list is produced), | |
| 58 | // has no "yield" | |
| 59 | ||
| 60 | for (n <- (1 to 10)) println(n) | |
| 61 | ||
| 62 | ||
| 63 | for (n <- (1 to 10)) {
 | |
| 64 |   print("The number is: ")
 | |
| 65 | print(n) | |
| 66 |   print("\n")
 | |
| 67 | } | |
| 68 | ||
| 69 | ||
| 70 | ||
| 71 | ||
| 72 | // know when to use yield and when not: | |
| 73 | ||
| 74 | for (e <- Set(1, 2, 3, 4, 5, 6, 7, 8, 9); if e < 5) yield square(e) | |
| 75 | ||
| 76 | ||
| 51 | 77 | // Option type | 
| 78 | //============= | |
| 53 | 79 | |
| 147 | 80 | //in Java, if something unusually happens, you return null; | 
| 53 | 81 | //in Scala you use Option | 
| 82 | // - if the value is present, you use Some(value) | |
| 83 | // - if no value is present, you use None | |
| 84 | ||
| 85 | ||
| 86 | List(7,2,3,4,5,6).find(_ < 4) | |
| 87 | List(5,6,7,8,9).find(_ < 4) | |
| 88 | ||
| 58 | 89 | |
| 147 | 90 | // some operations on Option's | 
| 58 | 91 | |
| 51 | 92 | val lst = List(None, Some(1), Some(2), None, Some(3)) | 
| 93 | ||
| 94 | lst.flatten | |
| 53 | 95 | |
| 51 | 96 | Some(1).get | 
| 97 | ||
| 53 | 98 | Some(1).isDefined | 
| 99 | None.isDefined | |
| 100 | ||
| 51 | 101 | val ps = List((3, 0), (3, 2), (4, 2), (2, 0), (1, 0), (1, 1)) | 
| 102 | ||
| 103 | for ((x, y) <- ps) yield {
 | |
| 104 | if (y == 0) None else Some(x / y) | |
| 105 | } | |
| 106 | ||
| 147 | 107 | // use .getOrElse is for setting a default value | 
| 53 | 108 | |
| 109 | val lst = List(None, Some(1), Some(2), None, Some(3)) | |
| 147 | 110 | |
| 57 | 111 | for (x <- lst) yield x.getOrElse(0) | 
| 112 | ||
| 113 | ||
| 53 | 114 | |
| 115 | ||
| 147 | 116 | // error handling with Options (no exceptions) | 
| 117 | // | |
| 118 | // Try(....) | |
| 57 | 119 | // | 
| 120 | // Try(something).getOrElse(what_to_do_in_an_exception) | |
| 121 | // | |
| 53 | 122 | import scala.util._ | 
| 147 | 123 | |
| 124 | Try(1 + 3) | |
| 125 | Try(9 / 0) | |
| 126 | ||
| 127 | Try(9 / 3).getOrElse(42) | |
| 128 | Try(9 / 0).getOrElse(42) | |
| 129 | ||
| 130 | ||
| 53 | 131 | import io.Source | 
| 132 | ||
| 147 | 133 | val my_url = """https://nms.kcl.ac.uk/christian.urban""" | 
| 134 | ||
| 135 | Source.fromURL(my_url).mkString | |
| 53 | 136 | |
| 147 | 137 | Try(Source.fromURL(my_url).mkString).getOrElse("")
 | 
| 53 | 138 | |
| 147 | 139 | Try(Some(Source.fromURL(my_url).mkString)).getOrElse(None) | 
| 140 | ||
| 53 | 141 | |
| 57 | 142 | // a function that turns strings into numbers | 
| 147 | 143 | Integer.parseInt("1234")
 | 
| 144 | ||
| 53 | 145 | |
| 146 | def get_me_an_int(s: String): Option[Int] = | |
| 147 | Try(Some(Integer.parseInt(s))).getOrElse(None) | |
| 148 | ||
| 149 | val lst = List("12345", "foo", "5432", "bar", "x21")
 | |
| 147 | 150 | |
| 53 | 151 | for (x <- lst) yield get_me_an_int(x) | 
| 152 | ||
| 153 | // summing all the numbers | |
| 147 | 154 | val sum = (for (i <- lst) yield get_me_an_int(i)).flatten.sum | 
| 53 | 155 | |
| 156 | ||
| 157 | // This may not look any better than working with null in Java, but to | |
| 158 | // see the value, you have to put yourself in the shoes of the | |
| 159 | // consumer of the get_me_an_int function, and imagine you didn't | |
| 160 | // write that function. | |
| 161 | // | |
| 162 | // In Java, if you didn't write this function, you'd have to depend on | |
| 147 | 163 | // the Javadoc of get_me_an_int. If you didn't look at the Javadoc, | 
| 57 | 164 | // you might not know that get_me_an_int could return a null, and your | 
| 165 | // code could potentially throw a NullPointerException. | |
| 53 | 166 | |
| 167 | ||
| 58 | 168 | // even Scala is not immune to problems like this: | 
| 169 | ||
| 170 | List(5,6,7,8,9).indexOf(7) | |
| 171 | ||
| 172 | ||
| 173 | ||
| 174 | ||
| 147 | 175 | // Higher-Order Functions | 
| 176 | //======================== | |
| 177 | ||
| 178 | // functions can take functions as arguments | |
| 179 | ||
| 180 | val lst = (1 to 10).toList | |
| 181 | ||
| 182 | def even(x: Int) : Boolean = x % 2 == 0 | |
| 183 | def odd(x: Int) : Boolean = x % 2 == 1 | |
| 184 | ||
| 185 | lst.filter(x => even(x)) | |
| 186 | lst.filter(even(_)) | |
| 187 | lst.filter(even) | |
| 188 | ||
| 189 | lst.find(_ > 8) | |
| 190 | ||
| 191 | // map applies a function to each element of a list | |
| 192 | ||
| 193 | def square(x: Int): Int = x * x | |
| 194 | ||
| 195 | lst.map(square) | |
| 196 | ||
| 197 | lst.map(square).filter(_ > 4) | |
| 198 | ||
| 199 | lst.map(square).filter(_ > 4).map(square) | |
| 200 | ||
| 201 | // map works for most collection types, including sets | |
| 202 | Set(1, 3, 6).map(square) | |
| 203 | ||
| 204 | ||
| 205 | // Why could functions as arguments be useful? | |
| 206 | // | |
| 207 | // Consider the sum between a and b: | |
| 208 | ||
| 209 | def sumInts(a: Int, b: Int) : Int = | |
| 210 | if (a > b) 0 else a + sumInts(a + 1, b) | |
| 211 | ||
| 212 | ||
| 213 | sumInt(10, 16) | |
| 214 | ||
| 215 | // sum squares | |
| 216 | def square(n: Int) : Int = n * n | |
| 217 | ||
| 218 | def sumSquares(a: Int, b: Int) : Int = | |
| 219 | if (a > b) 0 else square(a) + sumSquares(a + 1, b) | |
| 220 | ||
| 221 | sumSquares(2, 6) | |
| 222 | ||
| 223 | ||
| 224 | // sum factorials | |
| 225 | def fact(n: Int) : Int = | |
| 226 | if (n == 0) 1 else n * fact(n - 1) | |
| 227 | ||
| 228 | def sumFacts(a: Int, b: Int) : Int = | |
| 229 | if (a > b) 0 else fact(a) + sumFacts(a + 1, b) | |
| 230 | ||
| 231 | sumFacts(2, 6) | |
| 232 | ||
| 233 | ||
| 234 | ||
| 235 | // You can see the pattern....can we simplify out work? | |
| 236 | // The type of functions from ints to ints: Int => Int | |
| 237 | ||
| 238 | def sum(f: Int => Int, a: Int, b: Int) : Int = {
 | |
| 239 | if (a > b) 0 | |
| 240 | else f(a) + sum(f, a + 1, b) | |
| 241 | } | |
| 242 | ||
| 243 | ||
| 244 | def sumSquares(a: Int, b: Int) : Int = sum(square, a, b) | |
| 245 | def sumFacts(a: Int, b: Int) : Int = sum(fact, a, b) | |
| 246 | ||
| 247 | // What should we do for sumInts? | |
| 248 | ||
| 249 | def id(n: Int) : Int = n | |
| 250 | def sumInts(a: Int, b: Int) : Int = sum(id, a, b) | |
| 251 | ||
| 252 | ||
| 253 | ||
| 254 | // Anonymous Functions: You can also write: | |
| 255 | ||
| 256 | def sumCubes(a: Int, b: Int) : Int = sum(x => x * x * x, a, b) | |
| 257 | def sumSquares(a: Int, b: Int) : Int = sum(x => x * x, a, b) | |
| 258 | def sumInts(a: Int, b: Int) : Int = sum(x => x, a, b) | |
| 259 | ||
| 260 | ||
| 261 | // other function types | |
| 262 | // | |
| 263 | // f1: (Int, Int) => Int | |
| 264 | // f2: List[String] => Option[Int] | |
| 265 | // ... | |
| 266 | ||
| 267 | ||
| 148 | 268 | // an aside: partial application | 
| 269 | ||
| 270 | def add(a: Int)(b: Int) : Int = a + b | |
| 271 | ||
| 272 | sum(add(2), 0, 2) | |
| 273 | sum(add(10), 0, 2) | |
| 274 | ||
| 275 | def add2(a: Int, b: Int) : Int = a + b | |
| 276 | sum(x => add2(2, x), 0, 2) | |
| 277 | sum(x => add2(10, x), 0, 2) | |
| 278 | ||
| 147 | 279 | // Function Composition | 
| 280 | //====================== | |
| 281 | ||
| 282 | // How could Higher-Order Functions and Options be helpful? | |
| 283 | ||
| 284 | def add_footer(msg: String) : String = msg ++ " - Sent from iOS" | |
| 285 | ||
| 286 | def valid_msg(msg: String) : Boolean = msg.size <= 140 | |
| 287 | ||
| 288 | def duplicate(s: String) : String = s ++ s | |
| 289 | ||
| 290 | // they compose nicely | |
| 291 | valid_msg(add_footer("Hello World"))
 | |
| 292 | valid_msg(duplicate(add_footer("Hello World")))
 | |
| 293 | ||
| 294 | ||
| 295 | // first_word: let's first do it the ugly Java way using null: | |
| 296 | ||
| 297 | def first_word(msg: String) : String = {
 | |
| 298 |   val words = msg.split(" ")
 | |
| 299 | if (words(0) != "") words(0) else null | |
| 300 | } | |
| 301 | ||
| 302 | duplicate(first_word("Hello World"))
 | |
| 303 | duplicate(first_word(""))
 | |
| 304 | ||
| 305 | def extended_duplicate(s: String) : String = | |
| 306 | if (s != null) s ++ s else null | |
| 307 | ||
| 308 | extended_duplicate(first_word(""))
 | |
| 309 | ||
| 310 | ||
| 311 | // Avoid always null! | |
| 312 | def better_first_word(msg: String) : Option[String] = {
 | |
| 313 |   val words = msg.split(" ")
 | |
| 314 | if (words(0) != "") Some(words(0)) else None | |
| 315 | } | |
| 316 | ||
| 317 | better_first_word("Hello World").map(duplicate)
 | |
| 318 | better_first_word("Hello World").map(duplicate).map(duplicate).map(valid_msg)
 | |
| 319 | ||
| 320 | better_first_word("").map(duplicate)
 | |
| 321 | better_first_word("").map(duplicate).map(valid_msg)
 | |
| 322 | ||
| 323 | ||
| 324 | ||
| 58 | 325 | |
| 53 | 326 | |
| 327 | ||
| 147 | 328 | // Implicits (Cool Feature) | 
| 329 | //========================= | |
| 57 | 330 | // | 
| 147 | 331 | // For example adding your own methods to Strings: | 
| 332 | // Imagine you want to increment strings, like | |
| 57 | 333 | // | 
| 334 | // "HAL".increment | |
| 335 | // | |
| 336 | // you can avoid ugly fudges, like a MyString, by | |
| 147 | 337 | // using implicit conversions. | 
| 57 | 338 | |
| 339 | ||
| 340 | implicit class MyString(s: String) {
 | |
| 341 | def increment = for (c <- s) yield (c + 1).toChar | |
| 342 | } | |
| 343 | ||
| 344 | "HAL".increment | |
| 345 | ||
| 346 | ||
| 147 | 347 | |
| 348 | // No returns in Scala | |
| 53 | 349 | //==================== | 
| 350 | ||
| 147 | 351 | // You should not use "return" in Scala: | 
| 53 | 352 | // | 
| 353 | // A return expression, when evaluated, abandons the | |
| 354 | // current computation and returns to the caller of the | |
| 355 | // function in which return appears." | |
| 356 | ||
| 357 | def sq1(x: Int): Int = x * x | |
| 358 | def sq2(x: Int): Int = return x * x | |
| 359 | ||
| 360 | def sumq(ls: List[Int]): Int = {
 | |
| 147 | 361 | ls.map(sq1).sum[Int] | 
| 53 | 362 | } | 
| 363 | ||
| 147 | 364 | sumq(List(1, 2, 3, 4)) | 
| 36 | 365 | |
| 57 | 366 | |
| 147 | 367 | |
| 368 | def sumq(ls: List[Int]): Int = {
 | |
| 369 | val sqs : List[Int] = for (x <- ls) yield (return x * x) | |
| 370 | sqs.sum | |
| 53 | 371 | } | 
| 372 | ||
| 55 | 373 | |
| 374 | ||
| 147 | 375 | |
| 53 | 376 | // Pattern Matching | 
| 377 | //================== | |
| 378 | ||
| 379 | // A powerful tool which is supposed to come to Java in a few years | |
| 380 | // time (https://www.youtube.com/watch?v=oGll155-vuQ)...Scala already | |
| 381 | // has it for many years ;o) | |
| 382 | ||
| 383 | // The general schema: | |
| 384 | // | |
| 385 | //    expression match {
 | |
| 386 | // case pattern1 => expression1 | |
| 387 | // case pattern2 => expression2 | |
| 388 | // ... | |
| 389 | // case patternN => expressionN | |
| 390 | // } | |
| 391 | ||
| 392 | ||
| 393 | // remember | |
| 394 | val lst = List(None, Some(1), Some(2), None, Some(3)).flatten | |
| 395 | ||
| 396 | ||
| 397 | def my_flatten(xs: List[Option[Int]]): List[Int] = {
 | |
| 398 | ... | |
| 399 | } | |
| 400 | ||
| 401 | ||
| 57 | 402 | |
| 403 | ||
| 404 | ||
| 53 | 405 | def my_flatten(lst: List[Option[Int]]): List[Int] = lst match {
 | 
| 406 | case Nil => Nil | |
| 407 | case None::xs => my_flatten(xs) | |
| 408 | case Some(n)::xs => n::my_flatten(xs) | |
| 409 | } | |
| 410 | ||
| 411 | ||
| 412 | // another example | |
| 413 | def get_me_a_string(n: Int): String = n match {
 | |
| 414 | case 0 => "zero" | |
| 415 | case 1 => "one" | |
| 416 | case 2 => "two" | |
| 417 | case _ => "many" | |
| 418 | } | |
| 419 | ||
| 57 | 420 | get_me_a_string(0) | 
| 421 | ||
| 76 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 422 | // you can also have cases combined | 
| 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 423 | def season(month: String) = month match {
 | 
| 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 424 | case "March" | "April" | "May" => "It's spring" | 
| 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 425 | case "June" | "July" | "August" => "It's summer" | 
| 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 426 | case "September" | "October" | "November" => "It's autumn" | 
| 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 427 | case "December" | "January" | "February" => "It's winter" | 
| 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 428 | } | 
| 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 429 | |
| 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 430 | println(season("November"))
 | 
| 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 431 | |
| 77 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
76diff
changeset | 432 | // What happens if no case matches? | 
| 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
76diff
changeset | 433 | |
| 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
76diff
changeset | 434 | println(season("foobar"))
 | 
| 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
76diff
changeset | 435 | |
| 147 | 436 | |
| 55 | 437 | // User-defined Datatypes | 
| 438 | //======================== | |
| 439 | ||
| 147 | 440 | abstract class Colour | 
| 441 | case class Red() extends Colour | |
| 442 | case class Green() extends Colour | |
| 443 | case class Blue() extends Colour | |
| 57 | 444 | |
| 147 | 445 | def fav_colour(c: Colour) : Boolean = c match {
 | 
| 446 | case Red() => false | |
| 447 | case Green() => true | |
| 448 | case Blue() => false | |
| 55 | 449 | } | 
| 450 | ||
| 451 | ||
| 147 | 452 | // actually this can be written with "object" | 
| 61 | 453 | |
| 454 | ||
| 55 | 455 | // another example | 
| 147 | 456 | //================= | 
| 55 | 457 | |
| 458 | abstract class Person | |
| 459 | case class King() extends Person | |
| 460 | case class Peer(deg: String, terr: String, succ: Int) extends Person | |
| 461 | case class Knight(name: String) extends Person | |
| 462 | case class Peasant(name: String) extends Person | |
| 147 | 463 | |
| 55 | 464 | |
| 465 | def title(p: Person): String = p match {
 | |
| 466 | case King() => "His Majesty the King" | |
| 467 |   case Peer(deg, terr, _) => s"The ${deg} of ${terr}"
 | |
| 468 |   case Knight(name) => s"Sir ${name}"
 | |
| 469 | case Peasant(name) => name | |
| 470 | } | |
| 471 | ||
| 147 | 472 | |
| 55 | 473 | def superior(p1: Person, p2: Person): Boolean = (p1, p2) match {
 | 
| 474 | case (King(), _) => true | |
| 475 | case (Peer(_,_,_), Knight(_)) => true | |
| 476 | case (Peer(_,_,_), Peasant(_)) => true | |
| 477 | case (Peer(_,_,_), Clown()) => true | |
| 478 | case (Knight(_), Peasant(_)) => true | |
| 479 | case (Knight(_), Clown()) => true | |
| 480 | case (Clown(), Peasant(_)) => true | |
| 481 | case _ => false | |
| 482 | } | |
| 483 | ||
| 484 | val people = List(Knight("David"), 
 | |
| 57 | 485 |                   Peer("Duke", "Norfolk", 84), 
 | 
| 55 | 486 |                   Peasant("Christian"), 
 | 
| 487 | King(), | |
| 488 | Clown()) | |
| 489 | ||
| 57 | 490 | println(people.sortWith(superior(_, _)).mkString(", "))
 | 
| 491 | ||
| 492 | ||
| 53 | 493 | |
| 56 | 494 | |
| 95 
4fa7231fede7
added link file
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
77diff
changeset | 495 | |
| 
4fa7231fede7
added link file
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
77diff
changeset | 496 | |
| 147 | 497 | // Problems with mutability and parallel computations | 
| 498 | //==================================================== | |
| 56 | 499 | |
| 147 | 500 | def count_intersection(A: Set[Int], B: Set[Int]) : Int = {
 | 
| 501 | var count = 0 | |
| 502 | for (x <- A; if (B contains x)) count += 1 | |
| 503 | count | |
| 56 | 504 | } | 
| 505 | ||
| 147 | 506 | val A = (1 to 1000).toSet | 
| 507 | val B = (1 to 1000 by 4).toSet | |
| 508 | ||
| 509 | count_intersection(A, B) | |
| 510 | ||
| 511 | // but do not try to add .par to the for-loop above | |
| 512 | ||
| 513 | ||
| 514 | //propper parallel version | |
| 515 | def count_intersection2(A: Set[Int], B: Set[Int]) : Int = | |
| 516 | A.par.count(x => B contains x) | |
| 517 | ||
| 518 | count_intersection2(A, B) | |
| 519 | ||
| 520 | ||
| 521 | //for measuring time | |
| 522 | def time_needed[T](n: Int, code: => T) = {
 | |
| 523 | val start = System.nanoTime() | |
| 524 | for (i <- (0 to n)) code | |
| 525 | val end = System.nanoTime() | |
| 526 | (end - start) / 1.0e9 | |
| 56 | 527 | } | 
| 528 | ||
| 147 | 529 | val A = (1 to 1000000).toSet | 
| 530 | val B = (1 to 1000000 by 4).toSet | |
| 56 | 531 | |
| 147 | 532 | time_needed(10, count_intersection(A, B)) | 
| 533 | time_needed(10, count_intersection2(A, B)) | |
| 534 | ||
| 535 | ||
| 148 | 536 | // Type abbreviations | 
| 537 | //==================== | |
| 538 | ||
| 539 | // some syntactic convenience | |
| 540 | ||
| 541 | type Pos = (int, Int) | |
| 542 | type Board = List[List[Int]] | |
| 543 | ||
| 56 | 544 | |
| 57 | 545 | |
| 546 | ||
| 53 | 547 | // Sudoku | 
| 548 | //======== | |
| 549 | ||
| 57 | 550 | // THE POINT OF THIS CODE IS NOT TO BE SUPER | 
| 551 | // EFFICIENT AND FAST, just explaining exhaustive | |
| 552 | // depth-first search | |
| 553 | ||
| 554 | ||
| 55 | 555 | val game0 = """.14.6.3.. | 
| 556 | |62...4..9 | |
| 557 | |.8..5.6.. | |
| 558 | |.6.2....3 | |
| 559 | |.7..1..5. | |
| 560 | |5....9.6. | |
| 561 | |..6.2..3. | |
| 562 | |1..5...92 | |
| 563 |               |..7.9.41.""".stripMargin.replaceAll("\\n", "")
 | |
| 564 | ||
| 565 | type Pos = (Int, Int) | |
| 566 | val EmptyValue = '.' | |
| 567 | val MaxValue = 9 | |
| 568 | ||
| 569 | val allValues = "123456789".toList | |
| 570 | val indexes = (0 to 8).toList | |
| 571 | ||
| 57 | 572 | |
| 573 | def empty(game: String) = game.indexOf(EmptyValue) | |
| 574 | def isDone(game: String) = empty(game) == -1 | |
| 575 | def emptyPosition(game: String) = (empty(game) % MaxValue, empty(game) / MaxValue) | |
| 576 | ||
| 55 | 577 | |
| 57 | 578 | def get_row(game: String, y: Int) = indexes.map(col => game(y * MaxValue + col)) | 
| 579 | def get_col(game: String, x: Int) = indexes.map(row => game(x + row * MaxValue)) | |
| 580 | ||
| 147 | 581 | get_row(game0, 3) | 
| 582 | get_col(game0, 0) | |
| 583 | ||
| 57 | 584 | def get_box(game: String, pos: Pos): List[Char] = {
 | 
| 55 | 585 | def base(p: Int): Int = (p / 3) * 3 | 
| 586 | val x0 = base(pos._1) | |
| 587 | val y0 = base(pos._2) | |
| 588 | val ys = (y0 until y0 + 3).toList | |
| 589 | (x0 until x0 + 3).toList.flatMap(x => ys.map(y => game(x + y * MaxValue))) | |
| 590 | } | |
| 591 | ||
| 147 | 592 | get_box(game0, (0, 0)) | 
| 593 | get_box(game0, (1, 1)) | |
| 594 | get_box(game0, (2, 1)) | |
| 55 | 595 | |
| 147 | 596 | // this is not mutable!! | 
| 55 | 597 | def update(game: String, pos: Int, value: Char): String = game.updated(pos, value) | 
| 598 | ||
| 599 | def toAvoid(game: String, pos: Pos): List[Char] = | |
| 57 | 600 | (get_col(game, pos._1) ++ get_row(game, pos._2) ++ get_box(game, pos)) | 
| 55 | 601 | |
| 147 | 602 | def candidates(game: String, pos: Pos): List[Char] = allValues.diff(toAvoid(game,pos)) | 
| 55 | 603 | |
| 604 | //candidates(game0, (0,0)) | |
| 605 | ||
| 147 | 606 | def pretty(game: String): String = | 
| 607 | "\n" + (game sliding (MaxValue, MaxValue) mkString "\n") | |
| 55 | 608 | |
| 609 | def search(game: String): List[String] = {
 | |
| 610 | if (isDone(game)) List(game) | |
| 147 | 611 |   else {
 | 
| 612 | val cs = candidates(game, emptyPosition(game)) | |
| 613 | cs.map(c => search(update(game, empty(game), c))).toList.flatten | |
| 614 | } | |
| 55 | 615 | } | 
| 616 | ||
| 147 | 617 | search(game0).map(pretty) | 
| 55 | 618 | |
| 619 | val game1 = """23.915... | |
| 620 | |...2..54. | |
| 621 | |6.7...... | |
| 622 | |..1.....9 | |
| 623 | |89.5.3.17 | |
| 624 | |5.....6.. | |
| 625 | |......9.5 | |
| 626 | |.16..7... | |
| 627 |               |...329..1""".stripMargin.replaceAll("\\n", "")
 | |
| 628 | ||
| 147 | 629 | search(game1).map(pretty) | 
| 57 | 630 | |
| 147 | 631 | // game that is in the hard(er) category | 
| 55 | 632 | val game2 = """8........ | 
| 633 | |..36..... | |
| 634 | |.7..9.2.. | |
| 635 | |.5...7... | |
| 636 | |....457.. | |
| 637 | |...1...3. | |
| 638 | |..1....68 | |
| 639 | |..85...1. | |
| 640 |               |.9....4..""".stripMargin.replaceAll("\\n", "")
 | |
| 641 | ||
| 642 | // game with multiple solutions | |
| 643 | val game3 = """.8...9743 | |
| 644 | |.5...8.1. | |
| 645 | |.1....... | |
| 646 | |8....5... | |
| 647 | |...8.4... | |
| 648 | |...3....6 | |
| 649 | |.......7. | |
| 650 | |.3.5...8. | |
| 651 |               |9724...5.""".stripMargin.replaceAll("\\n", "")
 | |
| 652 | ||
| 57 | 653 | |
| 147 | 654 | search(game2).map(pretty) | 
| 655 | search(game3).map(pretty) | |
| 55 | 656 | |
| 657 | // for measuring time | |
| 658 | def time_needed[T](i: Int, code: => T) = {
 | |
| 659 | val start = System.nanoTime() | |
| 660 | for (j <- 1 to i) code | |
| 661 | val end = System.nanoTime() | |
| 662 | ((end - start) / i / 1.0e9) + " secs" | |
| 663 | } | |
| 664 | ||
| 665 | search(game2).map(pretty) | |
| 57 | 666 | search(game3).distinct.length | 
| 147 | 667 | time_needed(1, search(game2)) | 
| 668 | time_needed(1, search(game3)) | |
| 55 | 669 | |
| 53 | 670 | |
| 671 | ||
| 672 | ||
| 39 | 673 |