Skip to content

Commit f9070f2

Browse files
committed
ref: refactor KnightTour implementation
1 parent 9e55c9d commit f9070f2

File tree

1 file changed

+82
-87
lines changed

1 file changed

+82
-87
lines changed

backtracking/knight_tour.py

+82-87
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,93 @@
1-
# Knight Tour Intro: https://www.youtube.com/watch?v=ab_dY3dZFHM
2-
31
from __future__ import annotations
42

53

6-
def get_valid_pos(position: tuple[int, int], n: int) -> list[tuple[int, int]]:
7-
"""
8-
Find all the valid positions a knight can move to from the current position.
9-
10-
>>> get_valid_pos((1, 3), 4)
11-
[(2, 1), (0, 1), (3, 2)]
12-
"""
13-
14-
y, x = position
15-
positions = [
16-
(y + 1, x + 2),
17-
(y - 1, x + 2),
18-
(y + 1, x - 2),
19-
(y - 1, x - 2),
20-
(y + 2, x + 1),
21-
(y + 2, x - 1),
22-
(y - 2, x + 1),
23-
(y - 2, x - 1),
24-
]
25-
permissible_positions = []
26-
27-
for position in positions:
28-
y_test, x_test = position
29-
if 0 <= y_test < n and 0 <= x_test < n:
30-
permissible_positions.append(position)
31-
32-
return permissible_positions
33-
34-
35-
def is_complete(board: list[list[int]]) -> bool:
4+
class KnightTour:
365
"""
37-
Check if the board (matrix) has been completely filled with non-zero values.
38-
39-
>>> is_complete([[1]])
40-
True
41-
42-
>>> is_complete([[1, 2], [3, 0]])
43-
False
6+
Represents a Knight's Tour problem solver.
447
"""
458

46-
return not any(elem == 0 for row in board for elem in row)
9+
@staticmethod
10+
def get_valid_moves(position: tuple[int, int], n: int) -> list[tuple[int, int]]:
11+
"""
12+
Find all the valid positions a knight can move to from the current position.
4713
48-
49-
def open_knight_tour_helper(
50-
board: list[list[int]], pos: tuple[int, int], curr: int
51-
) -> bool:
52-
"""
53-
Helper function to solve knight tour problem.
54-
"""
55-
56-
if is_complete(board):
57-
return True
58-
59-
for position in get_valid_pos(pos, len(board)):
14+
>>> KnightTour.get_valid_moves((1, 3), 4)
15+
[(2, 1), (0, 1), (3, 2)]
16+
"""
6017
y, x = position
61-
62-
if board[y][x] == 0:
63-
board[y][x] = curr + 1
64-
if open_knight_tour_helper(board, position, curr + 1):
65-
return True
66-
board[y][x] = 0
67-
68-
return False
69-
70-
71-
def open_knight_tour(n: int) -> list[list[int]]:
72-
"""
73-
Find the solution for the knight tour problem for a board of size n. Raises
74-
ValueError if the tour cannot be performed for the given size.
75-
76-
>>> open_knight_tour(1)
77-
[[1]]
78-
79-
>>> open_knight_tour(2)
80-
Traceback (most recent call last):
81-
...
82-
ValueError: Open Knight Tour cannot be performed on a board of size 2
83-
"""
84-
85-
board = [[0 for i in range(n)] for j in range(n)]
86-
87-
for i in range(n):
88-
for j in range(n):
89-
board[i][j] = 1
90-
if open_knight_tour_helper(board, (i, j), 1):
91-
return board
92-
board[i][j] = 0
93-
94-
msg = f"Open Knight Tour cannot be performed on a board of size {n}"
95-
raise ValueError(msg)
18+
moves = [
19+
(y + 1, x + 2),
20+
(y - 1, x + 2),
21+
(y + 1, x - 2),
22+
(y - 1, x - 2),
23+
(y + 2, x + 1),
24+
(y + 2, x - 1),
25+
(y - 2, x + 1),
26+
(y - 2, x - 1),
27+
]
28+
valid_moves = [(y, x) for y, x in moves if 0 <= y < n and 0 <= x < n]
29+
return valid_moves
30+
31+
@staticmethod
32+
def is_board_complete(board: list[list[int]]) -> bool:
33+
"""
34+
Check if the board (matrix) has been completely filled with non-zero values.
35+
36+
>>> KnightTour.is_board_complete([[1]])
37+
True
38+
>>> KnightTour.is_board_complete([[1, 2], [3, 0]])
39+
False
40+
"""
41+
return not any(elem == 0 for row in board for elem in row)
42+
43+
@staticmethod
44+
def solve_knight_tour(
45+
board: list[list[int]], pos: tuple[int, int], curr: int
46+
) -> bool:
47+
"""
48+
Helper function to solve knight tour problem.
49+
"""
50+
51+
if KnightTour.is_board_complete(board):
52+
return True
53+
54+
n = len(board)
55+
for move in KnightTour.get_valid_moves(pos, n):
56+
y, x = move
57+
if board[y][x] == 0:
58+
board[y][x] = curr + 1
59+
if KnightTour.solve_knight_tour(board, move, curr + 1):
60+
return True
61+
board[y][x] = 0
62+
63+
return False
64+
65+
@staticmethod
66+
def find_knight_tour(n: int) -> list[list[int]]:
67+
"""
68+
Find the solution for the knight tour problem for a board of size n. Raises
69+
ValueError if the tour cannot be performed for the given size.
70+
71+
>>> KnightTour.find_knight_tour(1)
72+
[[1]]
73+
>>> KnightTour.find_knight_tour(2)
74+
Traceback (most recent call last):
75+
...
76+
ValueError: Knight's tour cannot be performed on a board of size 2
77+
"""
78+
if n < 1:
79+
raise ValueError("Board size must be at least 1")
80+
81+
board = [[0 for _ in range(n)] for _ in range(n)]
82+
83+
for i in range(n):
84+
for j in range(n):
85+
board[i][j] = 1
86+
if KnightTour.solve_knight_tour(board, (i, j), 1):
87+
return board
88+
board[i][j] = 0
89+
error_message = f"Knight's tour cannot be performed on a board of size {n}"
90+
raise ValueError(error_message)
9691

9792

9893
if __name__ == "__main__":

0 commit comments

Comments
 (0)