You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

79 lines
2.6 KiB

from collections import namedtuple
from math import sqrt
from pathlib import Path
Point = namedtuple('Point', ['y', 'x'])
Node = namedtuple('Node', ['p', 'd'])
# set up inputs
text = Path('input').read_text('utf-8')
board_lines = text.splitlines()
width = len(board_lines[0])
board = list(map(list, board_lines))
start, end = Point(*divmod(text.find('S'), width + 1)), Point(*divmod(text.find('E'), width + 1))
# simple cost heuristic based on distance to end node
def cost_heuristic(p: Point) -> float:
return sqrt((p.x - end.x) ** 2 + (p.y - end.y) ** 2)
# walks path backwards, scans for nodes with incoming paths of equal cost, and traverses all recursively
def equal_cost_backtrace(parent: dict, node_cost: dict, current: Node) -> set[Point]:
last_direction = current.d
trace = {current.p}
while current := parent.get(current):
trace.add(current.p)
for d in map(lambda o: (current.d + o) & 3, [1, -1]):
test = Node(current.p, d)
if last_direction != current.d and (node_cost.get(test, 0) - node_cost[current]) == 1000:
trace.update(equal_cost_backtrace(parent, node_cost, parent.get(test)))
last_direction = current.d
return trace
# simple A star
def a_star() -> tuple[int, int]:
t_start = Node(start, 0)
pending: set[Node] = {t_start}
parent: dict[Node, Node] = {}
node_cost: dict[Node, float] = {t_start: 0.0}
total_cost: dict[Node, float] = {t_start: cost_heuristic(t_start.p)}
while len(pending):
current = min(pending, key=lambda t: total_cost[t])
if current.p == end:
return int(node_cost[current]), len(equal_cost_backtrace(parent, node_cost, current))
pending.remove(current)
# scan in all directions
for i, vector in enumerate([(0, 1), (1, 0), (0, -1), (-1, 0)]):
neighbor = Node(Point(current.p.y + vector[0], current.p.x + vector[1]), i)
if board[neighbor.p.y][neighbor.p.x] == '#':
continue
# assign new cost based on rotation and step
new_cost = node_cost[current] + [0, 1000, 2000, 1000][(i - current.d) & 3] + 1
if new_cost >= node_cost.get(neighbor, 1e13):
continue
parent[neighbor] = current
node_cost[neighbor] = new_cost
total_cost[neighbor] = new_cost + cost_heuristic(neighbor.p)
if neighbor in pending:
continue
pending.add(neighbor)
return 0, 0
lowest_cost, nodes = a_star()
print("Part 1:", lowest_cost)
print("Part 2:", nodes)