generated from public/aoc_template
101 lines
2.8 KiB
Python
101 lines
2.8 KiB
Python
from __future__ import annotations
|
|
from collections import defaultdict
|
|
from tools.aoc import AOCDay
|
|
from tools.coordinate import Coordinate
|
|
from tools.grid import Grid
|
|
from typing import Any
|
|
|
|
|
|
ICE = {
|
|
'>': Coordinate(1, 0),
|
|
'v': Coordinate(0, 1)
|
|
}
|
|
|
|
|
|
class GraphNode(Coordinate):
|
|
def __init__(self, x: int, y: int, z: int = None):
|
|
self.children = {}
|
|
|
|
|
|
def build_graph(grid: Grid, part2: bool = False) -> GraphNode:
|
|
root = GraphNode(1, 0)
|
|
crossings = {}
|
|
end = Coordinate(grid.maxX, grid.maxY)
|
|
|
|
def traverse_grid(node: GraphNode, next_coord: Coordinate):
|
|
count = 1
|
|
n = set(grid.getNeighboursOf(next_coord, includeDefault=False, includeDiagonal=False))
|
|
last_coord = node
|
|
while len(n - {last_coord}) == 1:
|
|
count += 1
|
|
last_coord, next_coord = next_coord, (n - {last_coord}).pop()
|
|
n = set(grid.getNeighboursOf(next_coord, includeDefault=False, includeDiagonal=False))
|
|
|
|
if len(n) == 1: # dead end
|
|
if next_coord == end:
|
|
node.children[GraphNode(*next_coord)] = count
|
|
else: # crossing
|
|
if next_coord in crossings:
|
|
if part2:
|
|
crossings[next_coord].children[node] = count
|
|
node.children[crossings[next_coord]] = count
|
|
else:
|
|
this_node = GraphNode(*next_coord)
|
|
crossings[next_coord] = this_node
|
|
node.children[this_node] = count
|
|
for c in n:
|
|
if part2 or grid.get(c) not in ICE or ICE[grid.get(c)] == c - next_coord:
|
|
traverse_grid(this_node, c)
|
|
|
|
traverse_grid(root, Coordinate(1, 1))
|
|
return root
|
|
|
|
|
|
def get_longest_path(root: GraphNode, looking_for: GraphNode) -> int:
|
|
D = defaultdict(int)
|
|
v = set()
|
|
|
|
def calc_dist(node: GraphNode, dist: int):
|
|
if node in v:
|
|
return
|
|
v.add(node)
|
|
if D[node] < dist:
|
|
D[node] = dist
|
|
|
|
for c_node, c_dist in node.children.items():
|
|
calc_dist(c_node, dist + c_dist)
|
|
|
|
v.remove(node)
|
|
|
|
calc_dist(root, 0)
|
|
return D[looking_for]
|
|
|
|
|
|
class Day(AOCDay):
|
|
inputs = [
|
|
[
|
|
(94, "input23_test"),
|
|
(2326, "input23"),
|
|
],
|
|
[
|
|
(154, "input23_test"),
|
|
(6574, "input23"),
|
|
]
|
|
]
|
|
|
|
def parse_input(self, part2: bool = False) -> (GraphNode, Coordinate):
|
|
grid = Grid.from_data(self.getInput(), translate={'#': False})
|
|
root = build_graph(grid, part2=part2)
|
|
return root, Coordinate(grid.maxX, grid.maxY)
|
|
|
|
def part1(self) -> Any:
|
|
return get_longest_path(*self.parse_input())
|
|
|
|
def part2(self) -> Any:
|
|
return get_longest_path(*self.parse_input(part2=True))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
day = Day(2023, 23)
|
|
day.run(verbose=True)
|