-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgenerate_tours_quadrant.py
175 lines (138 loc) · 4.16 KB
/
generate_tours_quadrant.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
"""
This script generates a million unique solutions
from the first quadrant of an 8 x 8 chessboard.
@author: akash
"""
import os
import random
import polars as pl
from tqdm import tqdm
from datetime import date
random.seed(347923473)
current_date_formatted = date.today().strftime("%Y%m%d")
# dictionary mapping 2D to 1D indices
pos_to_int = {i: j for i, j in zip(
[(i, j) for i in range(8) for j in range(8)],
range(64)
)}
# defining possible offsets
possible_steps_offset = [
(2, 1), (1, 2), (-1, 2), (-2, 1),
(-2, -1), (-1, -2), (1, -2), (2, -1)
]
def valid_move(
x,
y,
board
):
return 0 <= x < 8 and 0 <= y < 8 and board[y][x] == -1
def degree_of_move(
x,
y,
board
):
count = 0
for dx, dy in possible_steps_offset:
if valid_move(x + dx, y + dy, board):
count += 1
return count
def make_moves(
x,
y,
board
):
moves = []
for dx, dy in possible_steps_offset:
new_x, new_y = x + dx, y + dy
if valid_move(new_x, new_y, board):
moves.append((new_x, new_y))
# applying a two-degree heuristic
moves.sort(key=lambda move: degree_of_move(move[0], move[1], board))
# randomizing the top two moves
random.shuffle(moves[:2])
return moves
def kt(
initial_position=(0, 0),
solutions_limit=1000
):
board = [
[-1 for _ in range(8)]
for _ in range(8)
]
# defining the starting position
x, y = initial_position
move_number = 0
board[y][x] = move_number
# adding the initial position to the path
path = [(x, y)]
solutions = []
def backtrack(x, y, move_number):
# when 64 moves are played, a tuple of the solution is saved
if move_number == 8 * 8:
solutions.append(tuple(path))
pbar.update(1)
# terminate the recursion when the given number of solutions are found
if len(solutions) >= solutions_limit:
return True
return False
for new_x, new_y in make_moves(x, y, board):
board[new_y][new_x] = move_number + 1
path.append((new_x, new_y))
# perform backtracking if the knight hits a dead end
if backtrack(new_x, new_y, move_number + 1):
return True
board[new_y][new_x] = -1
path.pop()
return False
# setting a progress counter
with tqdm(total=solutions_limit, desc="Solutions found") as pbar:
backtrack(x, y, move_number)
# return solutions as a tuple of tuples
return tuple(tuple(pos_to_int[i] for i in solutions[j]) for j in range(len(solutions)))
def save_as_parquet(
tours,
output_directory="./data/"
):
"""
:param tours: The completed Knight's tours as a list of sequences
:param output_directory: The directory to save the solutions
:return: A compressed parquet file with the solved tours
"""
total_moves = len(tours[0])
# int8 schema to reduce file size
as_df = pl.DataFrame(
tours,
schema={f"move_{i}": pl.Int8 for i in range(total_moves)},
orient="row"
)
# file path and name with metadata
file_path = (
f"{output_directory}"
f"/tours_"
f"{8}x{8}_"
f"{1000000}_"
f"{current_date_formatted}_"
f"recursive_"
f"{''.join(str(i) for i in current_date_formatted)}.parquet"
)
try:
# if the file already exists, concatenate new results and save it
if os.path.exists(file_path):
existing_df = pl.read_parquet(file_path)
as_df = pl.concat([existing_df, as_df])
as_df.write_parquet(
file_path,
compression="zstd"
)
except Exception as e:
print(f"File could not be saved due to the following error: {e}")
# list of positions from which tours should be generated
pos_to_sample = [
(0,0), (0, 1), (0, 2), (0, 3),
(1, 0), (1, 1), (1, 2), (1, 3),
(2, 0), (2, 1), (2, 2), (2, 3),
(3, 0), (3, 1), (3, 2), (3, 3)
]
tours_to_generate = 1_000_000
for i in pos_to_sample:
save_as_parquet(kt(i, solutions_limit=tours_to_generate))