| author | Christian Urban <urbanc@in.tum.de> | 
| Fri, 17 Nov 2017 02:13:40 +0000 | |
| changeset 147 | 3e5d8657302f | 
| parent 95 | 4fa7231fede7 | 
| child 148 | fc72f3ab3a57 | 
| permissions | -rw-r--r-- | 
| 51 | 1 | // Scala Lecture 2 | 
| 2 | //================= | |
| 3 | ||
| 4 | ||
| 147 | 5 | // Overloaded math operations | 
| 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 | ||
| 268 | // Function Composition | |
| 269 | //====================== | |
| 270 | ||
| 271 | // How could Higher-Order Functions and Options be helpful? | |
| 272 | ||
| 273 | def add_footer(msg: String) : String = msg ++ " - Sent from iOS" | |
| 274 | ||
| 275 | def valid_msg(msg: String) : Boolean = msg.size <= 140 | |
| 276 | ||
| 277 | def duplicate(s: String) : String = s ++ s | |
| 278 | ||
| 279 | // they compose nicely | |
| 280 | valid_msg(add_footer("Hello World"))
 | |
| 281 | valid_msg(duplicate(add_footer("Hello World")))
 | |
| 282 | ||
| 283 | ||
| 284 | // first_word: let's first do it the ugly Java way using null: | |
| 285 | ||
| 286 | def first_word(msg: String) : String = {
 | |
| 287 |   val words = msg.split(" ")
 | |
| 288 | if (words(0) != "") words(0) else null | |
| 289 | } | |
| 290 | ||
| 291 | duplicate(first_word("Hello World"))
 | |
| 292 | duplicate(first_word(""))
 | |
| 293 | ||
| 294 | def extended_duplicate(s: String) : String = | |
| 295 | if (s != null) s ++ s else null | |
| 296 | ||
| 297 | extended_duplicate(first_word(""))
 | |
| 298 | ||
| 299 | ||
| 300 | // Avoid always null! | |
| 301 | def better_first_word(msg: String) : Option[String] = {
 | |
| 302 |   val words = msg.split(" ")
 | |
| 303 | if (words(0) != "") Some(words(0)) else None | |
| 304 | } | |
| 305 | ||
| 306 | better_first_word("Hello World").map(duplicate)
 | |
| 307 | better_first_word("Hello World").map(duplicate).map(duplicate).map(valid_msg)
 | |
| 308 | ||
| 309 | better_first_word("").map(duplicate)
 | |
| 310 | better_first_word("").map(duplicate).map(valid_msg)
 | |
| 311 | ||
| 312 | ||
| 313 | ||
| 58 | 314 | |
| 53 | 315 | // Type abbreviations | 
| 316 | //==================== | |
| 317 | ||
| 318 | // some syntactic convenience | |
| 147 | 319 | |
| 53 | 320 | type Pos = (int, Int) | 
| 321 | type Board = List[List[Int]] | |
| 322 | ||
| 323 | ||
| 324 | ||
| 147 | 325 | // Implicits (Cool Feature) | 
| 326 | //========================= | |
| 57 | 327 | // | 
| 147 | 328 | // For example adding your own methods to Strings: | 
| 329 | // Imagine you want to increment strings, like | |
| 57 | 330 | // | 
| 331 | // "HAL".increment | |
| 332 | // | |
| 333 | // you can avoid ugly fudges, like a MyString, by | |
| 147 | 334 | // using implicit conversions. | 
| 57 | 335 | |
| 336 | ||
| 337 | implicit class MyString(s: String) {
 | |
| 338 | def increment = for (c <- s) yield (c + 1).toChar | |
| 339 | } | |
| 340 | ||
| 341 | "HAL".increment | |
| 342 | ||
| 343 | ||
| 147 | 344 | |
| 345 | // No returns in Scala | |
| 53 | 346 | //==================== | 
| 347 | ||
| 147 | 348 | // You should not use "return" in Scala: | 
| 53 | 349 | // | 
| 350 | // A return expression, when evaluated, abandons the | |
| 351 | // current computation and returns to the caller of the | |
| 352 | // function in which return appears." | |
| 353 | ||
| 354 | def sq1(x: Int): Int = x * x | |
| 355 | def sq2(x: Int): Int = return x * x | |
| 356 | ||
| 357 | def sumq(ls: List[Int]): Int = {
 | |
| 147 | 358 | ls.map(sq1).sum[Int] | 
| 53 | 359 | } | 
| 360 | ||
| 147 | 361 | sumq(List(1, 2, 3, 4)) | 
| 36 | 362 | |
| 57 | 363 | |
| 147 | 364 | |
| 365 | def sumq(ls: List[Int]): Int = {
 | |
| 366 | val sqs : List[Int] = for (x <- ls) yield (return x * x) | |
| 367 | sqs.sum | |
| 53 | 368 | } | 
| 369 | ||
| 55 | 370 | |
| 371 | ||
| 147 | 372 | |
| 53 | 373 | // Pattern Matching | 
| 374 | //================== | |
| 375 | ||
| 376 | // A powerful tool which is supposed to come to Java in a few years | |
| 377 | // time (https://www.youtube.com/watch?v=oGll155-vuQ)...Scala already | |
| 378 | // has it for many years ;o) | |
| 379 | ||
| 380 | // The general schema: | |
| 381 | // | |
| 382 | //    expression match {
 | |
| 383 | // case pattern1 => expression1 | |
| 384 | // case pattern2 => expression2 | |
| 385 | // ... | |
| 386 | // case patternN => expressionN | |
| 387 | // } | |
| 388 | ||
| 389 | ||
| 390 | // remember | |
| 391 | val lst = List(None, Some(1), Some(2), None, Some(3)).flatten | |
| 392 | ||
| 393 | ||
| 394 | def my_flatten(xs: List[Option[Int]]): List[Int] = {
 | |
| 395 | ... | |
| 396 | } | |
| 397 | ||
| 398 | ||
| 57 | 399 | |
| 400 | ||
| 401 | ||
| 53 | 402 | def my_flatten(lst: List[Option[Int]]): List[Int] = lst match {
 | 
| 403 | case Nil => Nil | |
| 404 | case None::xs => my_flatten(xs) | |
| 405 | case Some(n)::xs => n::my_flatten(xs) | |
| 406 | } | |
| 407 | ||
| 408 | ||
| 409 | // another example | |
| 410 | def get_me_a_string(n: Int): String = n match {
 | |
| 411 | case 0 => "zero" | |
| 412 | case 1 => "one" | |
| 413 | case 2 => "two" | |
| 414 | case _ => "many" | |
| 415 | } | |
| 416 | ||
| 57 | 417 | get_me_a_string(0) | 
| 418 | ||
| 76 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 419 | // you can also have cases combined | 
| 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 420 | def season(month: String) = month match {
 | 
| 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 421 | case "March" | "April" | "May" => "It's spring" | 
| 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 422 | case "June" | "July" | "August" => "It's summer" | 
| 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 423 | case "September" | "October" | "November" => "It's autumn" | 
| 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 424 | case "December" | "January" | "February" => "It's winter" | 
| 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 425 | } | 
| 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 426 | |
| 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 427 | println(season("November"))
 | 
| 
bc0e0aa4dee1
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
61diff
changeset | 428 | |
| 77 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
76diff
changeset | 429 | // What happens if no case matches? | 
| 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
76diff
changeset | 430 | |
| 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
76diff
changeset | 431 | println(season("foobar"))
 | 
| 
3cbe3d90b77f
updated
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
76diff
changeset | 432 | |
| 147 | 433 | |
| 55 | 434 | // User-defined Datatypes | 
| 435 | //======================== | |
| 436 | ||
| 147 | 437 | abstract class Colour | 
| 438 | case class Red() extends Colour | |
| 439 | case class Green() extends Colour | |
| 440 | case class Blue() extends Colour | |
| 57 | 441 | |
| 147 | 442 | def fav_colour(c: Colour) : Boolean = c match {
 | 
| 443 | case Red() => false | |
| 444 | case Green() => true | |
| 445 | case Blue() => false | |
| 55 | 446 | } | 
| 447 | ||
| 448 | ||
| 147 | 449 | // actually this can be written with "object" | 
| 61 | 450 | |
| 451 | ||
| 55 | 452 | // another example | 
| 147 | 453 | //================= | 
| 55 | 454 | |
| 455 | abstract class Person | |
| 456 | case class King() extends Person | |
| 457 | case class Peer(deg: String, terr: String, succ: Int) extends Person | |
| 458 | case class Knight(name: String) extends Person | |
| 459 | case class Peasant(name: String) extends Person | |
| 147 | 460 | |
| 55 | 461 | |
| 462 | def title(p: Person): String = p match {
 | |
| 463 | case King() => "His Majesty the King" | |
| 464 |   case Peer(deg, terr, _) => s"The ${deg} of ${terr}"
 | |
| 465 |   case Knight(name) => s"Sir ${name}"
 | |
| 466 | case Peasant(name) => name | |
| 467 | } | |
| 468 | ||
| 147 | 469 | |
| 55 | 470 | def superior(p1: Person, p2: Person): Boolean = (p1, p2) match {
 | 
| 471 | case (King(), _) => true | |
| 472 | case (Peer(_,_,_), Knight(_)) => true | |
| 473 | case (Peer(_,_,_), Peasant(_)) => true | |
| 474 | case (Peer(_,_,_), Clown()) => true | |
| 475 | case (Knight(_), Peasant(_)) => true | |
| 476 | case (Knight(_), Clown()) => true | |
| 477 | case (Clown(), Peasant(_)) => true | |
| 478 | case _ => false | |
| 479 | } | |
| 480 | ||
| 481 | val people = List(Knight("David"), 
 | |
| 57 | 482 |                   Peer("Duke", "Norfolk", 84), 
 | 
| 55 | 483 |                   Peasant("Christian"), 
 | 
| 484 | King(), | |
| 485 | Clown()) | |
| 486 | ||
| 57 | 487 | println(people.sortWith(superior(_, _)).mkString(", "))
 | 
| 488 | ||
| 489 | ||
| 53 | 490 | |
| 56 | 491 | |
| 95 
4fa7231fede7
added link file
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
77diff
changeset | 492 | |
| 
4fa7231fede7
added link file
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
77diff
changeset | 493 | |
| 147 | 494 | // Problems with mutability and parallel computations | 
| 495 | //==================================================== | |
| 56 | 496 | |
| 147 | 497 | def count_intersection(A: Set[Int], B: Set[Int]) : Int = {
 | 
| 498 | var count = 0 | |
| 499 | for (x <- A; if (B contains x)) count += 1 | |
| 500 | count | |
| 56 | 501 | } | 
| 502 | ||
| 147 | 503 | val A = (1 to 1000).toSet | 
| 504 | val B = (1 to 1000 by 4).toSet | |
| 505 | ||
| 506 | count_intersection(A, B) | |
| 507 | ||
| 508 | // but do not try to add .par to the for-loop above | |
| 509 | ||
| 510 | ||
| 511 | //propper parallel version | |
| 512 | def count_intersection2(A: Set[Int], B: Set[Int]) : Int = | |
| 513 | A.par.count(x => B contains x) | |
| 514 | ||
| 515 | count_intersection2(A, B) | |
| 516 | ||
| 517 | ||
| 518 | //for measuring time | |
| 519 | def time_needed[T](n: Int, code: => T) = {
 | |
| 520 | val start = System.nanoTime() | |
| 521 | for (i <- (0 to n)) code | |
| 522 | val end = System.nanoTime() | |
| 523 | (end - start) / 1.0e9 | |
| 56 | 524 | } | 
| 525 | ||
| 147 | 526 | val A = (1 to 1000000).toSet | 
| 527 | val B = (1 to 1000000 by 4).toSet | |
| 56 | 528 | |
| 147 | 529 | time_needed(10, count_intersection(A, B)) | 
| 530 | time_needed(10, count_intersection2(A, B)) | |
| 531 | ||
| 532 | ||
| 56 | 533 | |
| 57 | 534 | |
| 535 | ||
| 53 | 536 | // Sudoku | 
| 537 | //======== | |
| 538 | ||
| 57 | 539 | // THE POINT OF THIS CODE IS NOT TO BE SUPER | 
| 540 | // EFFICIENT AND FAST, just explaining exhaustive | |
| 541 | // depth-first search | |
| 542 | ||
| 543 | ||
| 55 | 544 | val game0 = """.14.6.3.. | 
| 545 | |62...4..9 | |
| 546 | |.8..5.6.. | |
| 547 | |.6.2....3 | |
| 548 | |.7..1..5. | |
| 549 | |5....9.6. | |
| 550 | |..6.2..3. | |
| 551 | |1..5...92 | |
| 552 |               |..7.9.41.""".stripMargin.replaceAll("\\n", "")
 | |
| 553 | ||
| 554 | type Pos = (Int, Int) | |
| 555 | val EmptyValue = '.' | |
| 556 | val MaxValue = 9 | |
| 557 | ||
| 558 | val allValues = "123456789".toList | |
| 559 | val indexes = (0 to 8).toList | |
| 560 | ||
| 57 | 561 | |
| 562 | def empty(game: String) = game.indexOf(EmptyValue) | |
| 563 | def isDone(game: String) = empty(game) == -1 | |
| 564 | def emptyPosition(game: String) = (empty(game) % MaxValue, empty(game) / MaxValue) | |
| 565 | ||
| 55 | 566 | |
| 57 | 567 | def get_row(game: String, y: Int) = indexes.map(col => game(y * MaxValue + col)) | 
| 568 | def get_col(game: String, x: Int) = indexes.map(row => game(x + row * MaxValue)) | |
| 569 | ||
| 147 | 570 | get_row(game0, 3) | 
| 571 | get_col(game0, 0) | |
| 572 | ||
| 57 | 573 | def get_box(game: String, pos: Pos): List[Char] = {
 | 
| 55 | 574 | def base(p: Int): Int = (p / 3) * 3 | 
| 575 | val x0 = base(pos._1) | |
| 576 | val y0 = base(pos._2) | |
| 577 | val ys = (y0 until y0 + 3).toList | |
| 578 | (x0 until x0 + 3).toList.flatMap(x => ys.map(y => game(x + y * MaxValue))) | |
| 579 | } | |
| 580 | ||
| 147 | 581 | get_box(game0, (0, 0)) | 
| 582 | get_box(game0, (1, 1)) | |
| 583 | get_box(game0, (2, 1)) | |
| 55 | 584 | |
| 147 | 585 | // this is not mutable!! | 
| 55 | 586 | def update(game: String, pos: Int, value: Char): String = game.updated(pos, value) | 
| 587 | ||
| 588 | def toAvoid(game: String, pos: Pos): List[Char] = | |
| 57 | 589 | (get_col(game, pos._1) ++ get_row(game, pos._2) ++ get_box(game, pos)) | 
| 55 | 590 | |
| 147 | 591 | def candidates(game: String, pos: Pos): List[Char] = allValues.diff(toAvoid(game,pos)) | 
| 55 | 592 | |
| 593 | //candidates(game0, (0,0)) | |
| 594 | ||
| 147 | 595 | def pretty(game: String): String = | 
| 596 | "\n" + (game sliding (MaxValue, MaxValue) mkString "\n") | |
| 55 | 597 | |
| 598 | def search(game: String): List[String] = {
 | |
| 599 | if (isDone(game)) List(game) | |
| 147 | 600 |   else {
 | 
| 601 | val cs = candidates(game, emptyPosition(game)) | |
| 602 | cs.map(c => search(update(game, empty(game), c))).toList.flatten | |
| 603 | } | |
| 55 | 604 | } | 
| 605 | ||
| 147 | 606 | search(game0).map(pretty) | 
| 55 | 607 | |
| 608 | val game1 = """23.915... | |
| 609 | |...2..54. | |
| 610 | |6.7...... | |
| 611 | |..1.....9 | |
| 612 | |89.5.3.17 | |
| 613 | |5.....6.. | |
| 614 | |......9.5 | |
| 615 | |.16..7... | |
| 616 |               |...329..1""".stripMargin.replaceAll("\\n", "")
 | |
| 617 | ||
| 147 | 618 | search(game1).map(pretty) | 
| 57 | 619 | |
| 147 | 620 | // game that is in the hard(er) category | 
| 55 | 621 | val game2 = """8........ | 
| 622 | |..36..... | |
| 623 | |.7..9.2.. | |
| 624 | |.5...7... | |
| 625 | |....457.. | |
| 626 | |...1...3. | |
| 627 | |..1....68 | |
| 628 | |..85...1. | |
| 629 |               |.9....4..""".stripMargin.replaceAll("\\n", "")
 | |
| 630 | ||
| 631 | // game with multiple solutions | |
| 632 | val game3 = """.8...9743 | |
| 633 | |.5...8.1. | |
| 634 | |.1....... | |
| 635 | |8....5... | |
| 636 | |...8.4... | |
| 637 | |...3....6 | |
| 638 | |.......7. | |
| 639 | |.3.5...8. | |
| 640 |               |9724...5.""".stripMargin.replaceAll("\\n", "")
 | |
| 641 | ||
| 57 | 642 | |
| 147 | 643 | search(game2).map(pretty) | 
| 644 | search(game3).map(pretty) | |
| 55 | 645 | |
| 646 | // for measuring time | |
| 647 | def time_needed[T](i: Int, code: => T) = {
 | |
| 648 | val start = System.nanoTime() | |
| 649 | for (j <- 1 to i) code | |
| 650 | val end = System.nanoTime() | |
| 651 | ((end - start) / i / 1.0e9) + " secs" | |
| 652 | } | |
| 653 | ||
| 654 | search(game2).map(pretty) | |
| 57 | 655 | search(game3).distinct.length | 
| 147 | 656 | time_needed(1, search(game2)) | 
| 657 | time_needed(1, search(game3)) | |
| 55 | 658 | |
| 53 | 659 | |
| 660 | ||
| 661 | ||
| 39 | 662 |