Skip to content

Commit d60d6b9

Browse files
author
Abhijit Sarkar
committed
Solve P91 - Knight's Tour
1 parent 5c26011 commit d60d6b9

File tree

6 files changed

+86
-3
lines changed

6 files changed

+86
-3
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ P80 (***) Conversions.
174174

175175
P90 (**) Eight queens problem
176176

177-
P91 (**) Knight’s tour.
177+
[P91](misc/src/P91.scala) (**) Knight’s tour.
178178

179179
P92 (***) Von Koch’s conjecture.
180180

build.mill

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,10 @@ trait NineNineModule extends ScalaModule with Cross.Module[String] with Scalafmt
3131
"-deprecation",
3232
"-unchecked",
3333
"-Wunused:all",
34-
"-rewrite",
3534
"-indent",
36-
"-source", "future",
35+
"-rewrite",
36+
"-source",
37+
"future",
3738
)
3839

3940
object test extends ScalaTests with TestModule.ScalaTest {

misc/src/P91.scala

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package misc
2+
// P91 (**) Knight’s tour.
3+
// Another famous problem is this one: How can a knight jump on an NxN chessboard in such a way
4+
// that it visits every square exactly once?
5+
//
6+
// Hints: Represent the squares by pairs of their coordinates of the form (X,Y), where both
7+
// X and Y are integers between 1 and N. (Alternately, define a Point class for the same purpose.)
8+
// Write a function jumps(N,(X,Y)) to list the squares that a knight can jump to from (X,Y) on a
9+
// NxN chessboard.And finally, represent the solution of our problem as a list of knight positions
10+
// (the knight’s tour).
11+
//
12+
// It might be nice to find more than one tour, but a computer will take a long time trying to find
13+
// them all at once. Can you make a lazy list that only calculates the tours as needed?
14+
//
15+
// Can you find only "closed tours", where the knight can jump from its final position back to its
16+
// starting position?
17+
object P91:
18+
private type Square = (x: Int, y: Int)
19+
private val moves: LazyList[Square] = LazyList(
20+
(2, 1),
21+
(1, 2),
22+
(-1, 2),
23+
(-2, 1),
24+
(-2, -1),
25+
(-1, -2),
26+
(1, -2),
27+
(2, -1)
28+
)
29+
30+
def allSquares(n: Int): LazyList[Square] = LazyList
31+
.iterate((x = 0, y = 0)) { curr =>
32+
if curr.y == n - 1 then (curr.x + 1, 0)
33+
else (curr.x, curr.y + 1)
34+
}
35+
.takeWhile(_.x < n)
36+
37+
def allKnightsTours(n: Int): LazyList[List[Square]] =
38+
allSquares(n)
39+
.map(knightsTour(n, Map.empty, _))
40+
41+
private def knightsTour(n: Int, visited: Map[Square, Int], curr: Square): List[Square] =
42+
// To find a closed tour, also check if the next moves contain the start square.
43+
if visited.size == n * n - 1 then visited.toList.sortBy(_._2).map(_._1) :+ curr
44+
else
45+
/*
46+
It is imperative that nextMoves is lazy,
47+
otherwise even after finding a tour,
48+
it keeps of trying to find others.
49+
*/
50+
val vs = visited + (curr -> (visited.size + 1))
51+
nextMoves(n, vs, curr)
52+
.sortBy(mv => nextMoves(n, vs, mv).size) // Warnsdorff's heuristic
53+
.map(nxt => knightsTour(n, vs, nxt))
54+
.find(_.size == n * n)
55+
.getOrElse(List.empty)
56+
57+
private def nextMoves(n: Int, visited: Map[Square, Int], curr: Square): LazyList[Square] =
58+
moves
59+
.filter { mv =>
60+
val x = curr.x + mv.x
61+
val y = curr.y + mv.y
62+
x >= 0 && x < n && y >= 0 && y < n && !visited.contains((x, y))
63+
}
64+
.map(mv => (curr.x + mv.x, curr.y + mv.y))

misc/src/P95.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package misc
22
import math.Integral.Implicits.infixIntegralOps
33

4+
// P95 (**) English number words.
5+
// On financial documents, like checks, numbers must sometimes be written in full words.
6+
// Example: 175 must be written as one-seven-five.
7+
// Write a function fullWords(num: Int) to print (non-negative) integer numbers in full words.
48
object P95:
59
private val Digit2Word = List(
610
(1, "one"),

misc/test/src/P91Spec.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package misc
2+
3+
import org.scalatest.funspec.AnyFunSpec
4+
import org.scalatest.matchers.should.Matchers.{contain, should}
5+
import org.scalatest.Inspectors.forAll
6+
7+
class P91Spec extends AnyFunSpec:
8+
9+
it("knights tours"):
10+
forAll(P91.allKnightsTours(8).take(2)) { tour =>
11+
tour should contain theSameElementsAs P91.allSquares(8).toList
12+
}

misc/test/src/P95Spec.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
package misc
2+
13
import misc.P95
24
import org.scalatest.funspec.AnyFunSpec
35
import org.scalatest.matchers.should.Matchers.shouldBe

0 commit comments

Comments
 (0)