Skip to content

Commit 7046de1

Browse files
committed
Added day 2019-18
1 parent dd568b5 commit 7046de1

File tree

1 file changed

+332
-0
lines changed

1 file changed

+332
-0
lines changed

2019/18-Many-Worlds Interpretation.py

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
# -------------------------------- Input data ---------------------------------------- #
2+
import os, pathfinding, heapq
3+
4+
from complex_utils import *
5+
6+
test_data = {}
7+
8+
test = 1
9+
test_data[test] = {
10+
"input": """#########
11+
#b.A.@.a#
12+
#########""",
13+
"expected": ["8", "Unknown"],
14+
}
15+
16+
test += 1
17+
test_data[test] = {
18+
"input": """########################
19+
#f.D.E.e.C.b.A.@.a.B.c.#
20+
######################.#
21+
#d.....................#
22+
########################""",
23+
"expected": ["86", "Unknown"],
24+
}
25+
26+
test += 1
27+
test_data[test] = {
28+
"input": """########################
29+
#...............b.C.D.f#
30+
#.######################
31+
#.....@.a.B.c.d.A.e.F.g#
32+
########################""",
33+
"expected": ["132", "Unknown"],
34+
}
35+
36+
test += 1
37+
test_data[test] = {
38+
"input": """#################
39+
#i.G..c...e..H.p#
40+
########.########
41+
#j.A..b...f..D.o#
42+
########@########
43+
#k.E..a...g..B.n#
44+
########.########
45+
#l.F..d...h..C.m#
46+
#################""",
47+
"expected": ["136", "Unknown"],
48+
}
49+
50+
test += 1
51+
test_data[test] = {
52+
"input": """########################
53+
#@..............ac.GI.b#
54+
###d#e#f################
55+
###A#B#C################
56+
###g#h#i################
57+
########################""",
58+
"expected": ["81", "Unknown"],
59+
}
60+
61+
test += 1
62+
test_data[test] = {
63+
"input": """#######
64+
#a.#Cd#
65+
##...##
66+
##.@.##
67+
##...##
68+
#cB#Ab#
69+
#######""",
70+
"expected": ["Unknown", "8"],
71+
}
72+
73+
test += 1
74+
test_data[test] = {
75+
"input": """#############
76+
#DcBa.#.GhKl#
77+
#.###...#I###
78+
#e#d#.@.#j#k#
79+
###C#...###J#
80+
#fEbA.#.FgHi#
81+
#############""",
82+
"expected": ["Unknown", "32"],
83+
}
84+
85+
test += 1
86+
test_data[test] = {
87+
"input": """#############
88+
#g#f.D#..h#l#
89+
#F###e#E###.#
90+
#dCba...BcIJ#
91+
#####.@.#####
92+
#nK.L...G...#
93+
#M###N#H###.#
94+
#o#m..#i#jk.#
95+
#############""",
96+
"expected": ["Unknown", "72"],
97+
}
98+
99+
test = "real"
100+
input_file = os.path.join(
101+
os.path.dirname(__file__),
102+
"Inputs",
103+
os.path.basename(__file__).replace(".py", ".txt"),
104+
)
105+
test_data[test] = {
106+
"input": open(input_file, "r+").read().strip(),
107+
"expected": ["4844", "Unknown"],
108+
}
109+
110+
# -------------------------------- Control program execution ------------------------- #
111+
112+
case_to_test = "real"
113+
part_to_test = 2
114+
115+
# -------------------------------- Initialize some variables ------------------------- #
116+
117+
puzzle_input = test_data[case_to_test]["input"]
118+
puzzle_expected_result = test_data[case_to_test]["expected"][part_to_test - 1]
119+
puzzle_actual_result = "Unknown"
120+
121+
122+
# -------------------------------- Actual code execution ----------------------------- #
123+
def grid_to_vertices(self, grid, diagonals_allowed=False, wall="#"):
124+
self.vertices = {}
125+
y = 0
126+
127+
for line in grid.splitlines():
128+
for x in range(len(line)):
129+
if line[x] != wall:
130+
self.vertices[x - y * j] = line[x]
131+
y += 1
132+
133+
for source in self.vertices:
134+
for direction in directions_straight:
135+
target = source + direction
136+
if target in self.vertices:
137+
if source in self.edges:
138+
self.edges[source].append(target)
139+
else:
140+
self.edges[source] = [target]
141+
142+
return True
143+
144+
145+
pathfinding.Graph.grid_to_vertices = grid_to_vertices
146+
147+
148+
def breadth_first_search(self, start, end=None):
149+
current_distance = 0
150+
frontier = [(start, 0)]
151+
self.distance_from_start = {start: 0}
152+
self.came_from = {start: None}
153+
154+
while frontier:
155+
vertex, current_distance = frontier.pop(0)
156+
current_distance += 1
157+
neighbors = self.neighbors(vertex)
158+
if not neighbors:
159+
continue
160+
161+
# Stop search when reaching another object
162+
if self.vertices[vertex] not in (".", "@") and vertex != start:
163+
continue
164+
165+
for neighbor in neighbors:
166+
if neighbor in self.distance_from_start:
167+
continue
168+
# Adding for future examination
169+
frontier.append((neighbor, current_distance))
170+
171+
# Adding for final search
172+
self.distance_from_start[neighbor] = current_distance
173+
self.came_from[neighbor] = vertex
174+
175+
if neighbor == end:
176+
return True
177+
178+
if end:
179+
return True
180+
return False
181+
182+
183+
pathfinding.Graph.breadth_first_search = breadth_first_search
184+
185+
186+
def neighbors_part1(self, vertex):
187+
neighbors = {}
188+
for target_item in edges[vertex[0]]:
189+
if target_item == "@":
190+
neighbors[(target_item, vertex[1])] = edges[vertex[0]][target_item]
191+
elif target_item == target_item.lower():
192+
if target_item in vertex[1]:
193+
neighbors[(target_item, vertex[1])] = edges[vertex[0]][target_item]
194+
else:
195+
keys = "".join(sorted([x for x in vertex[1]] + [target_item]))
196+
neighbors[(target_item, keys)] = edges[vertex[0]][target_item]
197+
else:
198+
if target_item.lower() in vertex[1]:
199+
neighbors[(target_item, vertex[1])] = edges[vertex[0]][target_item]
200+
else:
201+
continue
202+
203+
return neighbors
204+
205+
206+
def neighbors_part2(self, vertex):
207+
neighbors = {}
208+
for robot in vertex[0]:
209+
for target_item in edges[robot]:
210+
new_position = vertex[0].replace(robot, target_item)
211+
distance = edges[robot][target_item]
212+
if target_item in "1234":
213+
neighbors[(new_position, vertex[1])] = distance
214+
elif target_item.islower():
215+
if target_item in vertex[1]:
216+
neighbors[(new_position, vertex[1])] = distance
217+
else:
218+
keys = "".join(sorted([x for x in vertex[1]] + [target_item]))
219+
neighbors[(new_position, keys)] = distance
220+
else:
221+
if target_item.lower() in vertex[1]:
222+
neighbors[(new_position, vertex[1])] = distance
223+
224+
return neighbors
225+
226+
227+
# Only the WeightedGraph method is replaced, so that it doesn't impact the first search
228+
if part_to_test == 1:
229+
pathfinding.WeightedGraph.neighbors = neighbors_part1
230+
else:
231+
pathfinding.WeightedGraph.neighbors = neighbors_part2
232+
233+
234+
def dijkstra(self, start, end=None):
235+
current_distance = 0
236+
frontier = [(0, start)]
237+
heapq.heapify(frontier)
238+
self.distance_from_start = {start: 0}
239+
self.came_from = {start: None}
240+
min_distance = float("inf")
241+
242+
while frontier:
243+
current_distance, vertex = heapq.heappop(frontier)
244+
245+
if current_distance > min_distance:
246+
continue
247+
248+
neighbors = self.neighbors(vertex)
249+
if not neighbors:
250+
continue
251+
252+
# print (vertex, min_distance, len(self.distance_from_start))
253+
254+
for neighbor, weight in neighbors.items():
255+
# We've already checked that node, and it's not better now
256+
if neighbor in self.distance_from_start and self.distance_from_start[
257+
neighbor
258+
] <= (current_distance + weight):
259+
continue
260+
261+
# Adding for future examination
262+
heapq.heappush(frontier, (current_distance + weight, neighbor))
263+
264+
# Adding for final search
265+
self.distance_from_start[neighbor] = current_distance + weight
266+
self.came_from[neighbor] = vertex
267+
268+
if len(neighbor[1]) == nb_keys:
269+
min_distance = min(min_distance, current_distance + weight)
270+
271+
return end is None or end in self.distance_from_start
272+
273+
274+
pathfinding.WeightedGraph.dijkstra = dijkstra
275+
276+
277+
maze = pathfinding.Graph()
278+
maze.grid_to_vertices(puzzle_input)
279+
280+
# First, simplify the maze to have only the important items (@, keys, doors)
281+
items = "abcdefghijklmnopqrstuvwxyz" + "abcdefghijklmnopqrstuvwxyz".upper() + "@"
282+
items = maze.grid_search(puzzle_input, items)
283+
nb_keys = len([x for x in items if x in "abcdefghijklmnopqrstuvwxyz"])
284+
285+
if part_to_test == 2:
286+
# Separate the start point
287+
start = items["@"][0]
288+
del items["@"]
289+
items["1"] = [start + northwest]
290+
items["2"] = [start + northeast]
291+
items["3"] = [start + southwest]
292+
items["4"] = [start + southeast]
293+
294+
for dir in directions_straight + [0]:
295+
maze.add_walls([start + dir])
296+
297+
298+
edges = {}
299+
for item in items:
300+
maze.reset_search()
301+
302+
maze.breadth_first_search(items[item][0])
303+
edges[item] = {}
304+
for other_item in items:
305+
if other_item == item:
306+
continue
307+
if items[other_item][0] in maze.distance_from_start:
308+
edges[item][other_item] = maze.distance_from_start[items[other_item][0]]
309+
310+
311+
# Then, perform Dijkstra on the simplified graph
312+
graph = pathfinding.WeightedGraph()
313+
graph.edges = edges
314+
graph.reset_search()
315+
if part_to_test == 1:
316+
graph.dijkstra(("@", ""))
317+
else:
318+
graph.dijkstra(("1234", ""))
319+
320+
puzzle_actual_result = min(
321+
[
322+
graph.distance_from_start[x]
323+
for x in graph.distance_from_start
324+
if len(x[1]) == nb_keys
325+
]
326+
)
327+
328+
329+
# -------------------------------- Outputs / results --------------------------------- #
330+
331+
print("Expected result : " + str(puzzle_expected_result))
332+
print("Actual result : " + str(puzzle_actual_result))

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy