diff options
Diffstat (limited to '2018/aoc2018-d22.py')
-rw-r--r-- | 2018/aoc2018-d22.py | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/2018/aoc2018-d22.py b/2018/aoc2018-d22.py new file mode 100644 index 0000000..7dd7a86 --- /dev/null +++ b/2018/aoc2018-d22.py @@ -0,0 +1,136 @@ +#advent of code 2018 +#day 22 +#part 1 and part 2 + +#at this point Im used to pathfinding problems, so I expected an easy 2-star +#I started with a recursive djikstra-algo function, but unfortunately +#for the actual input, I was reaching the maximum number of recurrences +#my plan B was a queue, which in general was correct, but I was missing one detail +#the problem isnt 2D pathfinding, but 4D (width, depth, time AND equipment) +#to discover this I needed to look up a solution on reddit +#said solution reminded me of heapq module which I already intended to learn +#the results were great - my initial no-heapq code needed almost 10 minutes to finish +#while with heapq whole program ran for less than half a second +#while I tried to compensate for the equipment variable with some +#"guess which equipment is probably the best choice when traversing" +#it didnt really help as much as heapq +#anyway, I still wasnt getting the correct answer +#This time the issue was in the design: +#in order to account for regions that are outside the initial scope +#(within target coordinates range) I simply scaled up the whole map by constant factor +#for factor = 2 the map still wasnt big enough to create the shortest path +#I needed to scale it up by factoer = 6 to get the correct result +#while this scale-up strategy isnt *that* bad for square-ish maps +#this map is extremely deep, but not very wide +#I really liked the idea to calculate the region type on the spot for part2 +#which I noticed only after struggling for 3 hours to fix my code +#might redo the part2 to fix this +#if the code doesnt provide correct results for part 2, try changing the SCALE variable +#to a bigger number +# + + +import time; +import heapq; + +f = open("input.txt",'r'); +Testing = False; +#Testing = True; +if Testing: + f.close(); + f = open("testinput.txt",'r'); + +depth = eval(f.readline().replace("depth: ","")); +target = tuple(eval(f.readline().replace("target: ",""))); +f.close(); +#determine the type of region and other parameters +class region: + def __init__(self, pos, erosion1, erosion2): + self.Distance = 99999; #for when I tried to use djikstra + self.Equipped = None; + self.pos = pos; + if pos == (0,0) or pos == target: self.geo = 0; + elif pos[0] == 0: self.geo = pos[1]*48271; + elif pos[1] == 0: self.geo = pos[0]*16807; + else: self.geo = erosion1*erosion2; + self.erosion = (self.geo + depth)%20183; + self.risk = self.erosion%3; + if (self.risk == 0): + self.type = "R"; + self.Gear = ["T","C"]; + elif (self.risk == 1): + self.type = "W"; + self.Gear = ["C","N"]; + elif (self.risk == 2): + self.type = "N"; + self.Gear = ["T","N"]; + def GetAdjacent(self, grid): #hard to read, should remake this func, but not feeling like it + nextcords = []; + nx, ny = self.pos[0]-1, self.pos[1]; + if nx in range(SCALE*target[0] +1) and ny in range(SCALE*target[1] +1): nextcords.append((nx,ny)); + nx, ny = self.pos[0]+1, self.pos[1]; + if nx in range(SCALE*target[0] +1) and ny in range(SCALE*target[1] +1): nextcords.append((nx,ny)); + nx, ny = self.pos[0], self.pos[1]-1; + if nx in range(SCALE*target[0] +1) and ny in range(SCALE*target[1] +1): nextcords.append((nx,ny)); + nx, ny = self.pos[0], self.pos[1]+1; + if nx in range(SCALE*target[0] +1) and ny in range(SCALE*target[1] +1): nextcords.append((nx,ny)); + + return nextcords; + +area = {}; +mouth = (0,0); +part1 = 0; + +#for part 2: +#scale up the area with SCALE - wrong idea, see comment at the start +#SCALE is a variable in case it needs to be adjusted later +SCALE = 6; +#initiate area and calculate part 1 +for y in range(SCALE*target[1] +1): + for x in range(SCALE*target[0] +1): + ero1 = 0; + ero2 = 0; + r1 = area.get((x-1,y),None); + r2 = area.get((x,y-1),None); + if r1 != None: ero1 = r1.erosion; + if r2 != None: ero2 = r2.erosion; + area.update({(x,y):region((x,y),ero1,ero2)}); #brackets puke + if ( x in range(target[0] +1) and y in range(target[1] +1)): + part1 += area[(x,y)].risk; + +print("part 1 = ", part1); + +equip = "T"; +area[mouth].Distance = 0; +area[mouth].Equipped = equip; +gears = ["T","C","N"]; +queue = [(0, mouth, "T")]; +distances = {}; #obviously I meant time, but technically the same thing + +t1 = time.time(); +while queue: + d, p, e = heapq.heappop(queue); + if (p[0],p[1],e) in distances and distances[(p[0],p[1],e)] <= d: continue; + distances[(p[0],p[1],e)] = d; #update distances with the shorter path + #if the current region is the target and we're holding a torch - finish loop + if p == target and e == "T": + area[p].Equipped = e; + area[p].Distance = d; + break; + #push to queue the same region but with changed gear + for g in gears: + if g != e and g in area[p].Gear: + heapq.heappush(queue, (d+7,p,g)); + #push to queue the adjacent regions + neighbours = area[p].GetAdjacent(area); + for neighbour in neighbours: + if not e in area[neighbour].Gear: continue; + heapq.heappush(queue,(d+1,neighbour,e)); + +t2= time.time(); + +part2 = area[target].Distance; +print("part2 = ", part2); +print("\tpart 2 runtime ", t2-t1, "s"); + + |