Solve 2023:17 p1-2 "Clumsy Crucible"
Revenge, blast from the past etc! Had to learn Dijkstra's for this one. Was as stuck as one can be on AOC. This stopped my progress 2023, and kept me busy at least 20-30h this year (2024) as well. I never came past part 1, but got part 2 in minutes when I hit home. Turns out the initial queue was to blame, after studying Dijkstras, reading hints on the subreddit and tutorials in blogs. I was off-by-1 in costs, since I misplaced a read from the grid. I also struggled a really, really long time with a bug where I resetted the steps to aggresively. What helped me to figure it out was to create simpler test case grids and step-debug them. Example 1: 241 321 should give a least heat loss of 6 in pt1. Example 2: 11199 12199 99199 99131 99111 should give a least heat loss of 9 in pt2.
This commit is contained in:
parent
fb468c2199
commit
178d96494a
2 changed files with 156 additions and 0 deletions
|
|
@ -80,3 +80,59 @@ def vdbg(seen, h, w):
|
||||||
"""Print-debug visited positions of a matrix"""
|
"""Print-debug visited positions of a matrix"""
|
||||||
for r in range(h):
|
for r in range(h):
|
||||||
print("".join(["#" if (r, c) in seen else "." for c in range(w)]))
|
print("".join(["#" if (r, c) in seen else "." for c in range(w)]))
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
1. Create an array that holds the distance of each vertex from the starting
|
||||||
|
vertex. Initially, set this distance to infinity for all vertices except
|
||||||
|
the starting vertex which should be set to 0.
|
||||||
|
2. Create a priority queue (heap) and insert the starting vertex with its
|
||||||
|
distance of 0.
|
||||||
|
3. While there are still vertices left in the priority queue, select the vertex
|
||||||
|
with the smallest recorded distance from the starting vertex and visit its
|
||||||
|
neighboring vertices.
|
||||||
|
4. For each neighboring vertex, check if it is visited already or not. If it
|
||||||
|
isn’t visited yet, calculate its tentative distance by adding its weight
|
||||||
|
to the smallest distance found so far for its parent/previous node
|
||||||
|
(starting vertex in case of first-level vertices).
|
||||||
|
5. If this tentative distance is smaller than previously recorded value
|
||||||
|
(if any), update it in our ‘distances’ array.
|
||||||
|
6. Finally, add this visited vertex with its updated distance to our priority
|
||||||
|
queue and repeat step-3 until we have reached our destination or exhausted
|
||||||
|
all nodes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# https://pieriantraining.com/understanding-dijkstras-algorithm-in-python/
|
||||||
|
def dijkstra_algorithm(graph, start_node):
|
||||||
|
def min_distance(distances, visited):
|
||||||
|
min_val = float("inf")
|
||||||
|
min_index = -1
|
||||||
|
|
||||||
|
for i in range(len(distances)):
|
||||||
|
if distances[i] < min_val and i not in visited:
|
||||||
|
min_val = distances[i]
|
||||||
|
min_index = i
|
||||||
|
|
||||||
|
return min_index
|
||||||
|
|
||||||
|
num_nodes = len(graph)
|
||||||
|
|
||||||
|
distances = [float("inf")] * num_nodes
|
||||||
|
visited = []
|
||||||
|
|
||||||
|
distances[start_node] = 0
|
||||||
|
|
||||||
|
for i in range(num_nodes):
|
||||||
|
current_node = min_distance(distances, visited)
|
||||||
|
|
||||||
|
visited.append(current_node)
|
||||||
|
|
||||||
|
for j in range(num_nodes):
|
||||||
|
if graph[current_node][j] != 0:
|
||||||
|
|
||||||
|
new_distance = distances[current_node] + graph[current_node][j]
|
||||||
|
|
||||||
|
if new_distance < distances[j]:
|
||||||
|
distances[j] = new_distance
|
||||||
|
return distances
|
||||||
|
|
|
||||||
100
2023-python/output/day_17.py
Normal file
100
2023-python/output/day_17.py
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
from heapq import heappop, heappush
|
||||||
|
|
||||||
|
from output import answer # D, DD, ADJ, ints, mhd, mdbg, vdbg
|
||||||
|
|
||||||
|
n = 17
|
||||||
|
title = "Clumsy Crucible"
|
||||||
|
|
||||||
|
|
||||||
|
@answer(1, "Using std crucible, least heat loss incured: {}")
|
||||||
|
def part_1(presolved):
|
||||||
|
return presolved[0]
|
||||||
|
|
||||||
|
|
||||||
|
@answer(2, "Using ultra crucible, least heat loss incured: {}")
|
||||||
|
def part_2(presolved):
|
||||||
|
return presolved[1]
|
||||||
|
|
||||||
|
|
||||||
|
def solve(data):
|
||||||
|
grid = {
|
||||||
|
(r, c): int(col)
|
||||||
|
for r, row in enumerate(data.split())
|
||||||
|
for c, col in enumerate(row)
|
||||||
|
}
|
||||||
|
p1 = least_heat_loss(grid, 1, 3)
|
||||||
|
p2 = least_heat_loss(grid, 4, 10)
|
||||||
|
return p1, p2
|
||||||
|
|
||||||
|
|
||||||
|
def least_heat_loss(grid, minsteps, maxsteps):
|
||||||
|
target = max(grid)
|
||||||
|
seen = set()
|
||||||
|
queue = [(0, (0, 0), (0, 1), 0)]
|
||||||
|
while queue:
|
||||||
|
cost, pos, direction, steps = heappop(queue)
|
||||||
|
y, x = pos
|
||||||
|
dy, dx = direction
|
||||||
|
|
||||||
|
if pos == target:
|
||||||
|
return cost
|
||||||
|
|
||||||
|
if ((pos, direction, steps)) in seen:
|
||||||
|
continue
|
||||||
|
|
||||||
|
seen.add((pos, direction, steps))
|
||||||
|
|
||||||
|
if steps >= minsteps:
|
||||||
|
cwdy, cwdx = clockwise(*direction)
|
||||||
|
if (cw := (y + cwdy, x + cwdx)) in grid:
|
||||||
|
cwy, cwx = cw
|
||||||
|
heappush(queue, (cost + grid[cw], (cwy, cwx), (cwdy, cwdx), 1))
|
||||||
|
|
||||||
|
ccwdy, ccwdx = counterclockwise(*direction)
|
||||||
|
if (ccw := (y + ccwdy, x + ccwdx)) in grid:
|
||||||
|
ccwy, ccwx = ccw
|
||||||
|
heappush(queue, (cost + grid[ccw], (ccwy, ccwx), (ccwdy, ccwdx), 1))
|
||||||
|
|
||||||
|
if steps < maxsteps and (fwd := (y + dy, x + dx)) in grid:
|
||||||
|
fwdy, fwdx = fwd
|
||||||
|
heappush(queue, (cost + grid[fwd], (fwdy, fwdx), direction, steps + 1))
|
||||||
|
|
||||||
|
return -1
|
||||||
|
|
||||||
|
|
||||||
|
def clockwise(y, x):
|
||||||
|
"""
|
||||||
|
>>> clockwise(-1, 0)
|
||||||
|
(0, 1)
|
||||||
|
>>> clockwise(0, 1)
|
||||||
|
(1, 0)
|
||||||
|
>>> clockwise(1, 0)
|
||||||
|
(0, -1)
|
||||||
|
>>> clockwise(0, -1)
|
||||||
|
(-1, 0)
|
||||||
|
"""
|
||||||
|
return (x, y) if y == 0 else (x, -y)
|
||||||
|
|
||||||
|
|
||||||
|
def counterclockwise(y, x):
|
||||||
|
"""
|
||||||
|
>>> counterclockwise(-1, 0)
|
||||||
|
(0, -1)
|
||||||
|
>>> counterclockwise(0, -1)
|
||||||
|
(1, 0)
|
||||||
|
>>> counterclockwise(1, 0)
|
||||||
|
(0, 1)
|
||||||
|
>>> counterclockwise(0, 1)
|
||||||
|
(-1, 0)
|
||||||
|
"""
|
||||||
|
return (x, y) if x == 0 else (-x, y)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
with open("./input/17.txt", "r") as f:
|
||||||
|
inp = f.read().strip()
|
||||||
|
|
||||||
|
inp = solve(inp)
|
||||||
|
|
||||||
|
a = part_1(inp)
|
||||||
|
b = part_2(inp)
|
||||||
Loading…
Add table
Reference in a new issue