data = [] for line in open("input"): data.append(line.strip()) import sys, os; sys.path.append(os.path.join(os.path.dirname(__file__), "../lib")) import astar #print(*data, sep="\n") blizzards = {'>': {}, '<': {}, '^': {}, 'v': {}} for y in range(len(data)): for x in range(len(data[y])): c = data[y][x] if c in blizzards: blizzards[c][x,y] = 1 def show(t): H = len(data)-2 for y in range(len(data)): s = [] W = len(data[y])-2 for x in range(len(data[y])): c = data[y][x] if c == '#': s.append(c) else: bliz = [] u = 1 + ((x - t)-1)%W if (u,y) in blizzards['>']: bliz.append('>') u = 1 + ((x + t)-1)%W if (u,y) in blizzards['<']: bliz.append('<') v = 1 + ((y - t)-1)%H if (x,v) in blizzards['v']: bliz.append('v') v = 1 + ((y + t)-1)%H if (x,v) in blizzards['^']: bliz.append('^') if len(bliz) == 0: s.append('.') elif len(bliz) == 1: s.append(bliz[0]) elif len(bliz) < 10: s.append(str(len(bliz))) else: s.append('*') print(''.join(s)) def blocked(x,y,t): if not 0 <= y < len(data): return True if not 0 <= x < len(data[y]): return True if data[y][x] == '#': return True H = len(data)-2 W = len(data[y])-2 u = 1 + ((x - t)-1)%W if (u,y) in blizzards['>']: return True u = 1 + ((x + t)-1)%W if (u,y) in blizzards['<']: return True v = 1 + ((y - t)-1)%H if (x,v) in blizzards['v']: return True v = 1 + ((y + t)-1)%H if (x,v) in blizzards['^']: return True return False start = (data[0].index('.'), 0) end = (data[-1].index('.'), len(data)-1) leg_distance = abs(start[0] - end[0]) + abs(start[1] - end[1]) def is_goal(node, goal=end): x, y, t = node return (x,y) == goal def heuristic(node, goal=end): x, y, t = node return abs(goal[0] - x) + abs(goal[1] - y) def neighbors(node): x, y, t = node n = [] def check(dx,dy): if not blocked(x+dx,y+dy,t+1): n.append((1, (x+dx, y+dy, t+1))) check(+1,0) check(0,+1) check(-1,0) check(0,-1) check(0,0) return n show(0) show(1) #show((len(data)-2)*(len(data[0])-2)) from functools import partial import time t0 = time.time() # part 1 d1 = astar.search(start+(0,), is_goal, neighbors, heuristic)[0] t1 = time.time() # part 2 # we can always take the best path for each leg, # rather than trying to compute it over the whole trip. # suppose there is a better overall path B = d1' + d2 + d3 # with d1' > d1. we can "sync up" with this path by simply # waiting at the end square for d1'-d1 steps at the beginning # of the next leg. (A* will check this possibility for us.) # (the start and end squares are never blocked by blizzards.) # therefore even if we can complete later legs faster by # starting later, there is no downside to taking the shortest # path for all the previous legs. is_start = partial(is_goal, goal=start) heuristic2 = partial(heuristic, goal=start) d2 = astar.search(end+(d1,), is_start, neighbors, heuristic2)[0] d3 = astar.search(start+(d1+d2,), is_goal, neighbors, heuristic)[0] t2 = time.time() print("part 1", d1, t1 - t0) print("part 2", d1+d2+d3, t2 - t0, "(%+f)"%(t2-t1))