-
Notifications
You must be signed in to change notification settings - Fork 0
/
day18.py
156 lines (127 loc) · 4.16 KB
/
day18.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
# vi: set shiftwidth=4 tabstop=4 expandtab:
import datetime
import os
import string
import heapq
import collections
top_dir = os.path.dirname(os.path.abspath(__file__)) + "/../../"
def get_maze_from_file(file_path=top_dir + "resources/year2019_day18_input.txt"):
with open(file_path) as f:
return [l.strip() for l in f]
def is_entrance(c):
return c == "@"
def is_key(c):
return c in string.ascii_lowercase
def is_empty(c):
return c == "."
def is_free(c, keys):
return c != "#" and (not c.isupper() or c.lower() in keys)
# return is_empty(c) or is_key(c) or is_entrance(c) or c.lower() in keys
def at(maze, pos):
x, y = pos
return maze[x][y]
def find(maze, func):
for i, l in enumerate(maze):
for j, c in enumerate(l):
if func(c):
yield i, j
def find_entrance(maze):
entrances = list(find(maze, is_entrance))
assert len(entrances) == 1
return entrances[0]
def find_keys(maze):
return list(find(maze, is_key))
def distances_to_keys(maze, pos, keys):
# print("distances_to_keys:", keys_to_find)
# Dijkstra algorithm
distances = dict()
queue = collections.deque([(0, pos)])
neighbours = [(-1, 0), (+1, 0), (0, -1), (0, +1)]
while queue:
d, pos = queue.popleft()
distances[pos] = d
x, y = pos
d2 = d + 1
for dx, dy in neighbours:
pos2 = x + dx, y + dy
if pos2 not in distances:
cell = at(maze, pos2)
if is_free(cell, keys):
queue.append((d2, pos2))
if is_key(cell) and cell not in keys:
yield pos2, d2, cell
def get_all_keys(maze):
entrance = find_entrance(maze)
nb_keys_to_find = len(find_keys(maze))
# Create heap with values
# (distance travelled, -nb_keys_found, position, keys_found)
# ~~~~~~~~~~~~~~~~~~~~ <- enough to provide full state
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ <- the smaller, the better
heap = [(0, 0, entrance, frozenset())]
seen = dict()
while heap:
# For each state, check reachables keys and associated distances with Dijsktra
# add new state to heap
dist, nb_keys, pos, keys_found = heapq.heappop(heap)
state = (pos, keys_found)
state_seen = seen.get(state, None)
if state_seen:
assert state_seen <= dist
continue
seen[state] = dist
assert -len(keys_found) == nb_keys
if len(keys_found) == nb_keys_to_find:
return dist
for k_pos, d, new_key in distances_to_keys(maze, pos, keys_found):
assert new_key not in keys_found
dist2 = dist + d
keys_found2 = keys_found | frozenset([new_key])
heapq.heappush(heap, (dist2, -len(keys_found2), k_pos, keys_found2))
assert 0
def run_tests():
maze = [
"########################",
"#[email protected].#",
"######################.#",
"#d.....................#",
"########################",
]
assert get_all_keys(maze) == 86
maze = [
"########################",
"#...............b.C.D.f#",
"#.######################",
"#[email protected]#",
"########################",
]
assert get_all_keys(maze) == 132
maze = [
"#################",
"#i.G..c...e..H.p#",
"########.########",
"#j.A..b...f..D.o#",
"########@########",
"#k.E..a...g..B.n#",
"########.########",
"#l.F..d...h..C.m#",
"#################",
]
assert get_all_keys(maze) == 136
maze = [
"########################",
"#@..............ac.GI.b#",
"###d#e#f################",
"###A#B#C################",
"###g#h#i################",
"########################",
]
assert get_all_keys(maze) == 81
def get_solutions():
_ = get_maze_from_file()
# Too slow: print(get_all_keys(maze))
if __name__ == "__main__":
begin = datetime.datetime.now()
run_tests()
get_solutions()
end = datetime.datetime.now()
print(end - begin)