|
1 |
| -# Knight Tour Intro: https://www.youtube.com/watch?v=ab_dY3dZFHM |
2 |
| - |
3 | 1 | from __future__ import annotations
|
4 | 2 |
|
5 | 3 |
|
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: |
36 | 5 | """
|
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. |
44 | 7 | """
|
45 | 8 |
|
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. |
47 | 13 |
|
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 | + """ |
60 | 17 | 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) |
96 | 91 |
|
97 | 92 |
|
98 | 93 | if __name__ == "__main__":
|
|
0 commit comments