| 50 |      1 | // Part 3 about finding a single tour using the Warnsdorf Rule
 | 
|  |      2 | //=============================================================
 | 
|  |      3 | 
 | 
| 213 |      4 | 
 | 
|  |      5 | type Pos = (Int, Int)
 | 
|  |      6 | type Path = List[Pos]
 | 
|  |      7 | 
 | 
|  |      8 | // for measuring time
 | 
|  |      9 | def time_needed[T](n: Int, code: => T) : T = {
 | 
|  |     10 |   val start = System.nanoTime()
 | 
|  |     11 |   for (i <- 0 until n) code
 | 
|  |     12 |   val result = code
 | 
|  |     13 |   val end = System.nanoTime()
 | 
|  |     14 |   println(f"Time needed: ${(end - start) / 1.0e9}%3.3f secs.")
 | 
|  |     15 |   result
 | 
|  |     16 | }
 | 
|  |     17 | 
 | 
|  |     18 | def print_board(dim: Int, path: Path): Unit = {
 | 
|  |     19 |   println
 | 
|  |     20 |   for (i <- 0 until dim) {
 | 
|  |     21 |     for (j <- 0 until dim) {
 | 
|  |     22 |       print(f"${path.reverse.indexOf((i, j))}%4.0f ")
 | 
|  |     23 |     }
 | 
|  |     24 |     println
 | 
|  |     25 |   } 
 | 
|  |     26 | }
 | 
|  |     27 | 
 | 
|  |     28 | def add_pair(x: Pos, y: Pos): Pos = 
 | 
|  |     29 |   (x._1 + y._1, x._2 + y._2)
 | 
| 4 |     30 | 
 | 
| 213 |     31 | def is_legal(dim: Int, path: Path, x: Pos): Boolean = 
 | 
|  |     32 |   0 <= x._1 && 0 <= x._2 && x._1 < dim && x._2 < dim && !path.contains(x)
 | 
|  |     33 | 
 | 
|  |     34 | def moves(x: Pos): List[Pos] = 
 | 
|  |     35 |   List(( 1,  2),( 2,  1),( 2, -1),( 1, -2),
 | 
|  |     36 |        (-1, -2),(-2, -1),(-2,  1),(-1,  2)).map(add_pair(x, _))
 | 
|  |     37 | 
 | 
|  |     38 | def legal_moves(dim: Int, path: Path, x: Pos): List[Pos] = 
 | 
|  |     39 |   moves(x).filter(is_legal(dim, path, _))
 | 
|  |     40 | 
 | 
|  |     41 | def ordered_moves(dim: Int, path: Path, x: Pos): List[Pos] = 
 | 
|  |     42 |   legal_moves(dim, path, x).sortBy((x) => legal_moves(dim, path, x).length)
 | 
| 50 |     43 | 
 | 
| 213 |     44 | import scala.annotation.tailrec
 | 
|  |     45 | 
 | 
|  |     46 | @tailrec
 | 
|  |     47 | def first(xs: List[Pos], f: Pos => Option[Path]): Option[Path] = xs match {
 | 
|  |     48 |   case Nil => None
 | 
|  |     49 |   case x::xs => {
 | 
|  |     50 |     val result = f(x)
 | 
|  |     51 |     if (result.isDefined) result else first(xs, f)
 | 
|  |     52 |   }
 | 
|  |     53 | }
 | 
|  |     54 | 
 | 
|  |     55 | 
 | 
|  |     56 | //def first[A, B](xs: List[A], f: A => Option[B]): Option[B] =
 | 
|  |     57 | //  xs.flatMap(f(_)).headOption
 | 
|  |     58 | 
 | 
| 50 |     59 | 
 | 
| 213 |     60 | def first_closed_tour_heuristics(dim: Int, path: Path): Option[Path] = {
 | 
|  |     61 |   if (path.length == dim * dim && moves(path.head).contains(path.last)) Some(path)
 | 
|  |     62 |   else
 | 
|  |     63 |     first(ordered_moves(dim, path, path.head), (x: Pos) => first_closed_tour_heuristics(dim, x::path))
 | 
|  |     64 | }
 | 
|  |     65 | 
 | 
|  |     66 | // heuristic cannot be used to search for closed tours on 7 x 7
 | 
|  |     67 | for (dim <- 1 to 6) {
 | 
|  |     68 |   val t = time_needed(0, first_closed_tour_heuristics(dim, List((dim / 2, dim / 2))))
 | 
|  |     69 |   println(s"${dim} x ${dim} closed: " + (if (t == None) "" else { print_board(dim, t.get) ; "" }))
 | 
|  |     70 | }
 | 
|  |     71 | 
 | 
| 4 |     72 | 
 | 
| 213 |     73 | //@tailrec
 | 
|  |     74 | /*
 | 
|  |     75 | def first_tour_heuristics(dim: Int, path: Path): Option[Path] = {
 | 
|  |     76 | 
 | 
|  |     77 |   @tailrec
 | 
|  |     78 |   def aux(dim: Int, path: Path, moves: List[Pos]): Option[Path] = 
 | 
|  |     79 |   if (path.length == dim * dim) Some(path)
 | 
|  |     80 |   else
 | 
|  |     81 |     moves match {
 | 
|  |     82 |       case Nil => None
 | 
|  |     83 |       case x::xs => {
 | 
|  |     84 |         val r = first_tour_heuristics(dim, x::path)
 | 
|  |     85 |         if (r.isDefined) r else aux(dim, path, xs)
 | 
|  |     86 |       }    
 | 
|  |     87 |     }
 | 
|  |     88 | 
 | 
|  |     89 |   aux(dim, path, ordered_moves(dim, path, path.head)) 
 | 
|  |     90 | }
 | 
|  |     91 | */ 
 | 
| 50 |     92 | 
 | 
| 213 |     93 | @tailrec
 | 
|  |     94 | def tour_on_mega_board(dim: Int, paths: List[Path]): Option[Path] = paths match {
 | 
|  |     95 |   case Nil => None
 | 
|  |     96 |   case (path::rest) =>
 | 
|  |     97 |     if (path.length == dim * dim) Some(path)
 | 
|  |     98 |     else tour_on_mega_board(dim, ordered_moves(dim, path, path.head).map(_::path) ::: rest)
 | 
|  |     99 | }
 | 
|  |    100 | 
 | 
|  |    101 | 
 | 
| 4 |    102 | 
 | 
| 213 |    103 | /*
 | 
|  |    104 | def first_tour_heuristics(dim: Int, path: Path): Option[Path] = {
 | 
|  |    105 |   if (path.length == dim * dim) Some(path)
 | 
|  |    106 |   else
 | 
|  |    107 |     for (p <- ordered_moves(dim, path, path.head))
 | 
|  |    108 |       val r = first_tour_heuristics(dim, x::path)
 | 
|  |    109 |     //first(ordered_moves(dim, path, path.head), (x: Pos) => first_tour_heuristics(dim, x::path))
 | 
|  |    110 |     ordered_moves(dim, path, path.head).view.flatMap((x: Pos) => first_tour_heuristics(dim, x::path)).headOption
 | 
|  |    111 | }
 | 
|  |    112 | */ 
 | 
| 50 |    113 | 
 | 
| 213 |    114 | /*
 | 
|  |    115 | for (dim <- 1 to 50) {
 | 
|  |    116 |   val t = first_tour_heuristics(dim, List((dim / 2, dim / 2)))
 | 
|  |    117 |   println(s"${dim} x ${dim}: " + (if (t == None) "" else { print_board(dim, t.get) ; "" }))
 | 
|  |    118 | }
 | 
|  |    119 | */
 | 
|  |    120 | 
 | 
|  |    121 | val dim = 70
 | 
|  |    122 | println(s"${dim} x ${dim}:")
 | 
|  |    123 | print_board(dim, time_needed(0, tour_on_mega_board(dim, List(List((0, 0)))).get))
 | 
|  |    124 | 
 |