Skip to content

Commit

Permalink
Day 23
Browse files Browse the repository at this point in the history
  • Loading branch information
aastrand committed Dec 23, 2023
1 parent e1023da commit 9913bab
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 6 deletions.
180 changes: 180 additions & 0 deletions day23/day23.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
#!/usr/bin/env python3

import sys
from collections import defaultdict

from utils import io
from utils.graph import from_grid
from utils.grid import OFFSETS_STRAIGHT, Grid

sys.setrecursionlimit(2000)


def get_distances(graph, start):
def recur(graph, cur, visited, stack):
visited.add(cur)
for n in graph[cur]:
if n not in visited:
recur(graph, n, visited, stack)
stack.insert(0, cur)

visited = set()
stack = []
for pos in graph:
if pos not in visited:
recur(graph, pos, visited, stack)

dist = {}
for pos in stack:
dist[pos] = float("-inf")
dist[start] = 0

while stack:
i = stack.pop(0)

for node in graph[i]:
if dist[node] < dist[i] + 1:
dist[node] = dist[i] + 1

return dist


def part1(filename):
grid = Grid.from_lines(io.get_lines(filename))

start = (1, 0)
end = (grid.maxX - 1, grid.maxY)
# grid.set(start, "S")
# grid.set(end, "O")
# grid.print()

graph = defaultdict(set)
for y in range(grid.minY, grid.maxY + 1):
for x in range(grid.minX, grid.maxX + 1):
pos = (x, y)
val = grid.get(pos)
assert val in {".", ">", "v", "#"}

for o in OFFSETS_STRAIGHT:
neighbour = (x + o[0], y + o[1])
other = grid.get(neighbour)

if val in val in {".", ">", "v"} and other == ".":
graph[pos].add(neighbour)

if val == "." and other == ">" and o == (1, 0):
graph[pos].add(neighbour)

if val == "." and other == "v" and o == (0, 1):
graph[pos].add(neighbour)

dist = get_distances(graph, start)

return dist[end]


def simplify(graph, dist):
removals = set()
for node in set(graph.keys()):
if node in graph:
if len(graph[node]) == 2:
n1, n2 = graph[node]
if len(graph[n1]) <= 2 and len(graph[n2]) <= 2:
graph[node] = set()
removals.add(node)
graph[n1].remove(node)
graph[n2].remove(node)
graph[n1].add(n2)
graph[n2].add(n1)

# now n1 -> n2 and n2 -> n1
# update dists
dist[(n1, n2)] = dist[(n1, node)] + dist[(node, n2)]
dist[(n2, n1)] = dist[(n1, node)] + dist[(node, n2)]
del dist[(n1, node)]
del dist[(node, n1)]
del dist[(node, n2)]
del dist[(n2, node)]

for node in set(graph.keys()):
if node in graph:
if len(graph[node]) > 2:
for n in set(graph[node]):
removals.add(n)
graph[node].remove(n)

for on in graph[n]:
if on != node:
graph[on].remove(n)
graph[on].add(node)
graph[node].add(on)

# update dists
dist[(node, on)] = dist[(node, n)] + dist[(n, on)]
dist[(on, node)] = dist[(node, n)] + dist[(n, on)]
del dist[(node, n)]
del dist[(n, node)]
del dist[(n, on)]
del dist[(on, n)]

for r in removals:
del graph[r]

return graph, dist


def find_longest_path(graph, dist, start, end):
def dfs(node, visited):
if node == end:
return 0

if node in visited:
return float("-inf")

visited.add(node)

max_length = float("-inf")
for neighbor in graph[node]:
length = dist[(node, neighbor)] + dfs(neighbor, visited)
max_length = max(max_length, length)

visited.remove(node)

return max_length

return dfs(start, set())


def part2(filename):
grid = Grid.from_lines(io.get_lines(filename))

def visitor(graph, pos, val):
if val in {"v", ">"}:
graph.set(pos, ".")

grid.walk(visitor)

start = (1, 0)
end = (grid.maxX - 1, grid.maxY)
graph = from_grid(grid, lambda grid, pos, val, other: other == "." and val == ".")

dist = {}
for node in graph:
for n in graph[node]:
dist[(node, n)] = 1

graph, dist = simplify(graph, dist)

return find_longest_path(graph, dist, start, end)


def main():
assert part1("example.txt") == 94
print(part1("../input/2023/day23.txt"))

assert part2("example.txt") == 154
print(part2("../input/2023/day23.txt"))


if __name__ == "__main__":
sys.exit(main())
23 changes: 23 additions & 0 deletions day23/example.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#.#####################
#.......#########...###
#######.#########.#.###
###.....#.>.>.###.#.###
###v#####.#v#.###.#.###
###.>...#.#.#.....#...#
###v###.#.#.#########.#
###...#.#.#.......#...#
#####.#.#.#######.#.###
#.....#.#.#.......#...#
#.#####.#.#.#########v#
#.#...#...#...###...>.#
#.#.#v#######v###.###v#
#...#.>.#...>.>.#.###.#
#####v#.#.###v#.#.###.#
#.....#...#...#.#.#...#
#.#########.###.#.#.###
#...###...#...#...#.###
###.###.#.###v#####v###
#...#...#.#.>.>.#.>.###
#.###.###.#.###.#.#v###
#.....###...###...#...#
#####################.#
2 changes: 1 addition & 1 deletion input
Submodule input updated from c59574 to d7e26c
10 changes: 5 additions & 5 deletions utils/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,16 @@ def dijkstra(grid, starts, neighbours: lambda grid, cur: [], get_dist: lambda gr
heappush(pq, (0, start))

while len(pq) > 0:
(heat, cur) = heappop(pq)
(val, cur) = heappop(pq)

for n in neighbours(grid, cur):
if n not in dist:
new_heat = heat + get_dist(grid, cur, n)
if new_heat < dist.get(n, sys.maxsize):
new_val = val + get_dist(grid, cur, n)
if new_val < dist.get(n, sys.maxsize):
if n not in dist:
dist[n] = new_heat
dist[n] = new_val
prev[n] = cur
heappush(pq, (new_heat, n))
heappush(pq, (new_val, n))

return dist, prev

Expand Down

0 comments on commit 9913bab

Please sign in to comment.