diff options
author | b-idea <test@test.com> | 2023-10-20 23:22:25 +0200 |
---|---|---|
committer | b-idea <test@test.com> | 2023-10-20 23:22:25 +0200 |
commit | 61073dcec8e896b84fafbd6110e2da55a0dd2d5e (patch) | |
tree | 22f2f3c647f02102974df58f727601e0c66d429e | |
parent | 5b8623d06472ec9a4bcac8ea1e201b7ae528d7af (diff) |
added days: 15_p1, 17, 20, 21, 22, 23_p1, 24, 25
-rw-r--r-- | 2018/aoc2018-d15-p1.py | 324 | ||||
-rw-r--r-- | 2018/aoc2018-d17.py | 144 | ||||
-rw-r--r-- | 2018/aoc2018-d20.py | 115 | ||||
-rw-r--r-- | 2018/aoc2018-d21.py | 253 | ||||
-rw-r--r-- | 2018/aoc2018-d22.py | 136 | ||||
-rw-r--r-- | 2018/aoc2018-d23-p1.py | 47 | ||||
-rw-r--r-- | 2018/aoc2018-d24.py | 211 | ||||
-rw-r--r-- | 2018/aoc2018-d25.py | 69 |
8 files changed, 1299 insertions, 0 deletions
diff --git a/2018/aoc2018-d15-p1.py b/2018/aoc2018-d15-p1.py new file mode 100644 index 0000000..6a2665d --- /dev/null +++ b/2018/aoc2018-d15-p1.py @@ -0,0 +1,324 @@ +#advent of code 2018 +#day 13 +#part 1 +import heapq + +f = open("input.txt",'r'); +Testing = False; +Testing = True; +if Testing: + f.close(); + f = open("testinput.txt",'r'); +Testing2 = False; +Testing2 = True; +if Testing2: + f.close(); + f = open("testinput2.txt",'r'); +Testing3 = False; +#Testing3 = True; +if Testing3: + f.close(); + f = open("testinput3.txt",'r'); +Testing4 = False; +#Testing4 = True; +if Testing4: + f.close(); + f = open("testinput4.txt",'r'); +Testing5 = False; +Testing5 = True; +if Testing5: + f.close(); + f = open("testinput5.txt",'r'); +Testing6 = False; +Testing6 = True; +if Testing6: + f.close(); + f = open("testinput6.txt",'r'); +Testing7 = False; +Testing7 = True; +if Testing7: + f.close(); + f = open("testinput7.txt",'r'); +NotTesting = False; +NotTesting = True; +if NotTesting: + f.close(); + f = open("input.txt",'r'); + #f = open("shit.txt",'r'); + +class thing: + def __init__(self, Pos, sign, currentID): + self.sign = sign; + self.PosX = Pos[1]; + self.PosY = Pos[0]; + self.IsAlive = False; + if (sign == "."): + self.IsPassable = True; + else: + self.IsPassable = False; + def __str__(self): + return self.sign; + + def SquaresInRange(self,bg): + squares = []; + p = (self.PosY-1,self.PosX); + if(bg[p].IsPassable): squares.append(p); + p = (self.PosY,self.PosX-1); + if(bg[p].IsPassable): squares.append(p); + p = (self.PosY,self.PosX+1); + if(bg[p].IsPassable): squares.append(p); + p = (self.PosY+1,self.PosX); + if(bg[p].IsPassable): squares.append(p); + return squares; + +class creature(thing): + def __init__(self, Pos, sign, currentID): + thing.__init__(self, Pos, sign, currentID); + self.ID = currentID; + self.health = 200; + self.damage = 3; + self.side = sign; + self.IsAlive = True; + if (self.side == "G"): + self.enemy = "E"; + if (self.side == "E"): + self.enemy = "G"; + + def death(self,bg): + self.IsPassable = True; + self.IsAlive = False; + self.health = 0; + unitcords[(self.PosY,self.PosX)] = None; + bg[(self.PosY,self.PosX)].IsPassable = True; + + + def movement(self,bg,PosNow,PosNew): + self.PosX = PosNew[1]; + self.PosY = PosNew[0]; + bg[PosNow].IsPassable = True; + bg[PosNew].IsPassable = False; + unitcords[PosNew] = self.ID; + unitcords[PosNow] = None; + + + def ScanProximity(self): + targets = []; + p = (self.PosY-1,self.PosX); + if p in unitcords and unitcords[p] in units and units[unitcords[p]].side == self.enemy and units[unitcords[p]].health >0: + targets.append(p); + p = (self.PosY,self.PosX-1); + if p in unitcords and unitcords[p] in units and units[unitcords[p]].side == self.enemy and units[unitcords[p]].health >0: + targets.append(p); + p = (self.PosY,self.PosX+1); + if p in unitcords and unitcords[p] in units and units[unitcords[p]].side == self.enemy and units[unitcords[p]].health >0: + targets.append(p); + p = (self.PosY+1,self.PosX); + if p in unitcords and unitcords[p] in units and units[unitcords[p]].side == self.enemy and units[unitcords[p]].health >0: + targets.append(p); + if targets: + targets.sort(key = lambda x: (units[unitcords[x]].health,units[unitcords[x]].PosY,units[unitcords[x]].PosX)); + return targets; + + + def IdentifyTargets_ALL(self,creaturelist,bg): + points = []; + for c in creaturelist: + if units[c].side == self.side or not units[c].IsAlive: continue; + points.extend(units[c].SquaresInRange(bg)); + return points; + + + def ShortestPaths(self, pointlist, bg): + paths = []; + pathlen = len(bg); + queue = []; + heapq.heappush(queue, (0,[(self.PosY,self.PosX)])); + visited = []; + while queue: + d, path = heapq.heappop(queue); + if d > pathlen: + return paths; + if path[-1] in pointlist: + if pathlen > len(path): + paths.append(path); + pathlen = len(path); + continue; + if path[-1] in visited: + continue; + else: + visited.append(path[-1]); + for a in bg[path[-1]].SquaresInRange(bg): + if a in visited: continue; + heapq.heappush(queue, (d+1, path + [a])); + return paths; + + def ChosenPath(self, pointlist, bg): + paths = self.ShortestPaths(pointlist, bg); + if len(paths) != 0: + pathends = min([p[-1] for p in paths]); + paths2= self.ShortestPaths([pathends], bg); + if len(paths2) != 0: + moves = []; + for p in paths: + moves.append(p[1]); + moves = sorted(moves, key = lambda m: (m[0],m[1])); + m = moves[0]; + self.movement(bg,(self.PosY,self.PosX),m); + + +def PathSearch(bg, Coordinates, Destination): + pass; + +def strike(attacker, target,bg): + target.health -= attacker.damage; + if (target.health <= 0): + target.death(bg); + +def armystrength(army): + strength = 0; + for soldier in army: + strength += units[unitcords[soldier]].health; + return strength; + +def armyscores(): + ElfScore = 0; + GobScore = 0; + for u in units: + if units[u].side == "G": GobScore += units[u].health; + if units[u].side == "E": ElfScore += units[u].health; + + return max(ElfScore,GobScore); + + + +def FindEnemies(team): + Enemies = 0; + for u in units: + if units[u].IsAlive and units[u].side != team: Enemies += 1; + + return Enemies; +############################################################################### + + +bgsizex = 0; +bgsizey = 0; +battleground = {}; +units = {}; +unitcords = {}; + +identifier = 11; + +for y, line in enumerate(f): + bgsizey += 1; + bgsizex = 0; + for x, c in enumerate(line): + bgsizex += 1; + if c == "G" or c == "E": + battleground[(y,x)] = thing((y,x),".",identifier); + units[identifier] = creature((y,x),c, identifier); + units[identifier].movement(battleground, (y,x), (y,x)); + identifier += 1; + else: + battleground[(y,x)] = thing((y,x),c,identifier); +''' +for y in range(bgsizey): + for x in range(bgsizex): + if (y,x) in unitcords: + print("X",end=""); + else: + print(battleground[(y,x)],end=""); #''' + + +unitcords = {}; +for u in units: + unitcords[(units[u].PosY,units[u].PosX)] = units[u].ID; + + +elfs = sum([1 for x in units if units[x].IsAlive and units[x].side=="E"]); +goblins = sum([1 for x in units if units[x].IsAlive and units[x].side=="G"]); + +ElapsedRounds = 0; + + +while goblins and elfs: + fighters = []; + for u in units: + if units[u].IsAlive: + fighters.append(units[u].ID); + + fighters = sorted(fighters, key = lambda u: (units[u].PosY,units[u].PosX)); + + for fighter in fighters: + if not units[fighter].IsAlive: continue; + if FindEnemies(units[fighter].side) <= 0: + print("loop broken FIGHTERS"); + break; + tars = units[fighter].ScanProximity(); + if tars: + tar = tars[0]; + strike(units[fighter],units[unitcords[tar]],battleground); + else: + CurrentPos = (units[fighter].PosY,units[fighter].PosX); + IdenTars = units[fighter].IdentifyTargets_ALL(fighters,battleground); + units[fighter].ChosenPath(IdenTars,battleground); + if CurrentPos == (units[fighter].PosY,units[fighter].PosX): + continue; + else: + tars = units[fighter].ScanProximity(); + if tars: + tar = tars[0]; + strike(units[fighter],units[unitcords[tar]],battleground); + + unitcords = {}; + for u in units: + unitcords[(units[u].PosY,units[u].PosX)] = units[u].ID; + + print("ROUND ", ElapsedRounds); + unitcords = {}; + for u in units: + unitcords[(units[u].PosY,units[u].PosX)] = units[u].ID; + + ElapsedRounds += 1; + elfs = sum([1 for x in units if units[x].IsAlive and units[x].side=="E"]); + goblins = sum([1 for x in units if units[x].IsAlive and units[x].side=="G"]); + ''' + for y in range(bgsizey): + for x in range(bgsizex): + if (y,x) in unitcords and unitcords[(y,x)] in units and units[unitcords[(y,x)]].IsAlive: + print(units[unitcords[(y,x)]].side,end=""); + else: + print(battleground[(y,x)],end=""); + print(); + ''' + + ''' + for y in range(bgsizey): + for x in range(bgsizex): + print((y,x), " - ", battleground[(y,x)].IsPassable); + print(); + print(); + ''' + + + +unitcords = {}; +for u in units: + unitcords[(units[u].PosY,units[u].PosX)] = units[u].ID; + print(u, f'({units[u].side} / ({units[u].PosY},{units[u].PosX})) ', units[u].health); + + +for y in range(bgsizey): + for x in range(bgsizex): + if (y,x) in unitcords and unitcords[(y,x)] in units and units[unitcords[(y,x)]].IsAlive: + print(units[unitcords[(y,x)]].side,end=""); + else: + print(battleground[(y,x)],end=""); +print(); + + +print("part 1 = ", ElapsedRounds, "*",armyscores(), "=",ElapsedRounds*armyscores()); +print("lil cheat = ", (ElapsedRounds-1), "*",armyscores(), "=",(ElapsedRounds-1)*armyscores()); +#I cant get the correct answer without implementing any short circuit logics +#thats why I print 2 outputs, for number of rounds and (number of rounds -1) + +#190777 p1 correct diff --git a/2018/aoc2018-d17.py b/2018/aoc2018-d17.py new file mode 100644 index 0000000..30cd726 --- /dev/null +++ b/2018/aoc2018-d17.py @@ -0,0 +1,144 @@ +#advent of code 2018 +#day 17 +#part 1 and part 2 + +#for me this is one of the most time consuming puzzles for this year +#I got too cocky because I remembered a similar puzzle during 2022 edition +#unfortunately 2018 day 17 was more tricky +#I made 3 attempts, each time starting from scratch +#first time I just scrambled something, but there was no way to calculate when to stop the algo +#so the water was extremely overflowing +#for second attempt I wrote 2 functions, one for flowing downwards, second for spreading +#I spent a whole day "improving" the spreading function so it behaves correctly, to no avail +#I figured out the logic of the puzzle, but I kept getting weird errors +#such as water bursting through walls or flowing upwards +#I was adding more and more if-elses, but they weren't fixing these bugs: +#they were merely delaying them - after filling in the water correctly, the bugs were popping up again +#third and final attempt was successful - I already learned the logic of the algorithm, +#I just needed to implement it in bug-free way +#on the other hand, I managed to get a pretty good runtime. +#Every now and then I was looking up a solution for clues from others +#one of which Michael Fogleman, probably my most common resource for learning AoC 2018 +#while his code runs for almost a minute, mine just needs less than 0.05 secs +#it's really funny to "outperform" (in a way) someone whom you were constantly consulting for help +#one last thing I want to mention is that during the third attempt I kept getting wrong answer +#since I was only off by 3 and at the end there are only 3 waterfalls connecting to lowest point, +#I was convinced that I was calculating the solution for wrong range +#turns out I misunderstood the puzzle - you're supposed to calculate the amount of still +#and flowing water in range of highest wall point and the lowest wall point, +#while I kept calculating it in range from zero to lowest +#at least this time part2 was a freebee + +import time; + +f = open("input.txt",'r'); +Testing = False; +#Testing = True; +if Testing: + f.close(); + f = open("testinput.txt",'r'); + +def parse_input(line): + line = line.replace("x=",""); + line = line.replace("y=",""); + line = line.replace("..",","); + line = "[" + line + "]"; + cords = eval(line); + return cords; #line coordinates: [x, y1, y2] or [y, x1, x2] + +Xmax = 500; #rightmost X coordinate, just for printing the area +Xmin = 500; #leftmost X coordinate, just for printing the area +Ymax = 0; #lowest Y coordinate, needed for solution +Ymin2 = 2000; #highest Y coordinate, needed for solution +LinesX = []; +LinesY = []; + +for l in f: + d = l[0]; #direction x or y + Cords = parse_input(l); + if d == "x": + Xmax = max(Cords[0], Xmax); + Xmin = min(Cords[0], Xmin); + Ymax = max(Cords[2], Ymax); + Ymin2 = min(Cords[1], Ymin2); + LinesX.append(Cords); + else: + Xmax = max(Cords[2], Xmax); + Xmin = min(Cords[1], Xmin); + Ymax = max(Cords[0], Ymax); + Ymin2 = min(Cords[0], Ymin2); + LinesY.append(Cords); + +Xmax += 1; +Xmin -= 1; +Ymin = 0; + +area = {}; +#initialize input into the area +for l in LinesX: + x = l[0]; + for y in range(l[1],l[2]+1): + area.update({(x,y):"#"}); +for l in LinesY: + y = l[0]; + for x in range(l[1],l[2]+1): + area.update({(x,y):"#"}); + +def wspread(cords, q): + r1 = 0; + r2 = 0; + x,y = cords; + while (area.get((x,y+1)," ") == "#" or area.get((x,y+1)," ") == "=") and area.get((x-1,y)," ") != "#": + r1 += 1; + x -= 1; + + x,y = cords; + while (area.get((x,y+1)," ") == "#" or area.get((x,y+1)," ") == "=") and area.get((x+1,y)," ") != "#": + r2 += 1; + x += 1; + + x,y = cords; + if area.get((x-r1-1,y)," ") == "#" and area.get((x+r2+1,y)," ") == "#": + for r in range(x-r1,x+r2+1): + area[(r,y)] = "="; + q.append((cords[0],cords[1]-1)); + else: + for r in range(x-r1,x+r2+1): + area[(r,y)] = "|"; + if area[(x-r1,y)] != "#" and area.get((x-r1,y+1)," ") == " ": + q.append((x-r1,y)); + if area[(x+r2,y)] != "#" and area.get((x+r2,y+1)," ") == " ": + q.append((x+r2,y)); + +def wfall(cords, q): + x,y = cords; + while area.get((x,y+1)," ")==" " and y < Ymax: + area[(x,y+1)] = "|"; + y += 1; + if area.get((x,y)," ")=="#" or area.get((x,y)," ")=="=": + q.append((x,y-1)); + elif y < Ymax: + q.append((x,y)); + +t1 = time.time(); +water = [(500,0)]; +area.update({(500,0):"|"}); + +while water: + w = water.pop(); + if area.get((w[0],w[1]+1)," ")==" ": + wfall(w, water); + else: + wspread(w, water); + +part1 = 0; +part2 = 0; +for a in area: + part1 += 1*(area[a]=="|" or area[a]=="=")*(a[1] >= Ymin2); + part2 += 1*(area[a]=="="); + +t2 = time.time(); + +print("part1 = ", part1); +print("part2 = ", part2); +print("runtime ", t2-t1); diff --git a/2018/aoc2018-d20.py b/2018/aoc2018-d20.py new file mode 100644 index 0000000..06241f5 --- /dev/null +++ b/2018/aoc2018-d20.py @@ -0,0 +1,115 @@ +#advent of code 2018 +#day 20 +#part 1 and part 2 + +#I struggled with indexing when parsing the input +#I was getting more doors mapped than there really were +#the issue was that after parsing the content of a bracket +#I was retreiving a ")" sign instead of the following instruction like N or W +#+1 to the index did the work +#a bit disappointed with part 2, on which I spent more time reading it than coding + +f = open("input.txt",'r'); +Testing = False; +#Testing = True; +if Testing: + f.close(); + f = open("testinput.txt",'r'); + +inputline = f.readline(); +inputline = inputline[1:-1]; +f.close(); +#split the contents of a bracket into separate strings, return a list of those strings +def parse_brackets(regex): + regex = regex[1:]; + close_brackets_sign = ")"; + close_brackets_count = 1; + subsets = []; + subset = ""; + for i, c in enumerate(regex): + if c == "(": + close_brackets_count += 1; + subset += c; + elif c == close_brackets_sign: + if close_brackets_count == 1: + subsets.append(subset); + break; + else: + close_brackets_count -= 1; + subset += c; + elif c == "|": + if close_brackets_count == 1: + subsets.append(subset); + subset = ""; + else: subset += c; + else: subset += c; + return i, subsets; +#fill up the "area" dictionary with rooms and doors based on provided instruction +#recursive function, the function is called again if the path is branching due to brackets +def parse_input(inputline, Cords, grid): + i = 0; + d = (0,0); + CurrentCords = Cords; + CurrentCords = (CurrentCords[0] +d[0],CurrentCords[1] +d[1]); + while i < len(inputline): + c = inputline[i]; + if c == "(": + x, subs = parse_brackets(inputline[i:]); + for s in subs: + parse_input(s, CurrentCords, grid); + i += x+1; + elif c == "$": + break; + else: + if c == "N": + d = (0,-1); + elif c == "S": + d = (0,1); + elif c == "W": + d = (-1,0); + elif c == "E": + d = (1,0); + CurrentCords = (CurrentCords[0] +d[0],CurrentCords[1] +d[1]); + grid.update({CurrentCords:"D"}); + CurrentCords = (CurrentCords[0] +d[0],CurrentCords[1] +d[1]); + grid.update({CurrentCords:"."}); + i += 1; + +area = {}; +p = (0,0); +area.update({p:"."}); + +parse_input(inputline, p, area); +#print the map of the area +''' +maxX = max([x[0] for x in area]); +minX = min([x[0] for x in area]); +maxY = max([y[1] for y in area]); +minY = min([y[1] for y in area]); + +for y in range(minY-1, maxY+1+1): + for x in range(minX-1, maxX+1+1): + cp = area.get((x,y),"#"); + if x==0 and y==0: cp = "X"; + print(cp, end=""); + print(); +''' + +distances = {}; +queue = [(0,0,0)]; + +while queue: + x,y,d = queue.pop(); + if ((x,y) in distances and distances.get((x,y)) <= d): + continue; + distances.update({(x,y):d}); + if (x+1,y) in area and (x+2,y) in area: queue.append((x+2,y,d+1)); + if (x-1,y) in area and (x-2,y) in area: queue.append((x-2,y,d+1)); + if (x,y+1) in area and (x,y+2) in area: queue.append((x,y+2,d+1)); + if (x,y-1) in area and (x,y-2) in area: queue.append((x,y-2,d+1)); + +part1 = max([distances[dist] for dist in distances]); +print("part1 = ", part1); + +part2 =len([distances[dist] for dist in distances if distances[dist]>=1000]); +print("part2 = ", part2); diff --git a/2018/aoc2018-d21.py b/2018/aoc2018-d21.py new file mode 100644 index 0000000..890b13d --- /dev/null +++ b/2018/aoc2018-d21.py @@ -0,0 +1,253 @@ +#advent of code 2018 +#day 21 +#part 1 and part 2 + +#this puzzle in my opinion was poorly worded, or I'm just overthinking +#part 1 was suprisingly simple since it only required running the solution for day 19 +#unfortunately, bruteforcing the answer for part 2 didn't go as well +#reading through input its possible to identify instruction jumps and determine what causes them +#I was stuck for like 2 days for part 2: I couldn't find a bug in my reverse-engineering +#I even run someone else's solution (modified for my input) to verify it +#only to get another wrong answer +#I found a second solution and I compared it to mine +#only to find out that the issue wasn't in logic, I simply entered wrong value in 1 line +#at least this reverse-engineering puzzle was for me more enjoyable than the 2021 one + +#part 1 bruteforce +#program halts if the value at register 0 matches the value at register X +#(register X is between 1 and 5, depends on the input) +#in one particular instruction near the end +#instruction 28 in my case +#calculating this value provides part 1 result + +#part 2 reverse engineering +#you can simplify the input program by reverse-engineering +#basically the first few lines are trash, then the program is initiated with starting values +#if condition 1 is met, program enters a loop until condition 1 is no longer true +#after this loop, program checks if the value at register 0 is equal to corresponding register X +#if true, program halts +#you can print out all values at register X, first printed message is part1, last one is part 2 + + +###opcode functions +#bit modified version of day 19 +def addr(before, op): + bef = before.copy(); + C = bef[op[1]] + bef[op[2]]; + bef[op[3]] = C; + return bef; +def addi(before, op): + bef = before.copy(); + C = bef[op[1]] + op[2]; + bef[op[3]] = C; + return bef; + +def mulr(before, op): + bef = before.copy(); + C = bef[op[1]] * bef[op[2]]; + bef[op[3]] = C; + return bef; +def muli(before, op): + bef = before.copy(); + C = bef[op[1]] * op[2]; + bef[op[3]] = C; + return bef; + +def banr(before, op): + bef = before.copy(); + C = int(bef[op[1]] & bef[op[2]]); + bef[op[3]] = C; + return bef; +def bani(before, op): + bef = before.copy(); + C = int(bef[op[1]] & op[2]); + bef[op[3]] = C; + return bef; + +def borr(before, op): + bef = before.copy(); + C = int(bef[op[1]] | bef[op[2]]); + bef[op[3]] = C; + return bef; +def bori(before, op): + bef = before.copy(); + C = int(bef[op[1]] | op[2]); + bef[op[3]] = C; + return bef; + +def setr(before, op): + bef = before.copy(); + C = bef[op[1]]; + bef[op[3]] = C; + return bef; +def seti(before, op): + bef = before.copy(); + C = op[1]; + bef[op[3]] = C; + return bef; + +def gtir(before, op): + bef = before.copy(); + C = 0 + int( op[1] > bef[op[2]] ); + bef[op[3]] = C; + return bef; +def gtri(before, op): + bef = before.copy(); + C = 0 + int( op[2] < bef[op[1]] ); + bef[op[3]] = C; + return bef; +def gtrr(before, op): + bef = before.copy(); + C = 0 + int( bef[op[1]] > bef[op[2]] ); + bef[op[3]] = C; + return bef; + +def eqir(before, op): + bef = before.copy(); + C = 0 + int( op[1] == bef[op[2]] ); + bef[op[3]] = C; + return bef; +def eqri(before, op): + bef = before.copy(); + C = 0 + int( op[2] == bef[op[1]] ); + bef[op[3]] = C; + return bef; +def eqrr(before, op): + bef = before.copy(); + C = 0 + int( bef[op[1]] == bef[op[2]] ); + bef[op[3]] = C; + return bef; + +MatchFunctions = {}; +MatchFunctions.update({"addr":addr}); +MatchFunctions.update({"addi":addi}); +MatchFunctions.update({"mulr":mulr}); +MatchFunctions.update({"muli":muli}); +MatchFunctions.update({"banr":banr}); +MatchFunctions.update({"bani":bani}); +MatchFunctions.update({"borr":borr}); +MatchFunctions.update({"bori":bori}); +MatchFunctions.update({"setr":setr}); +MatchFunctions.update({"seti":seti}); +MatchFunctions.update({"gtir":gtir}); +MatchFunctions.update({"gtri":gtri}); +MatchFunctions.update({"gtrr":gtrr}); +MatchFunctions.update({"eqir":eqir}); +MatchFunctions.update({"eqri":eqri}); +MatchFunctions.update({"eqrr":eqrr}); + +#end of day19 copy-paste +############################################### + +f = open("input.txt",'r'); +Testing = False; +#Testing = True; +if Testing: + f.close(); + f = open("testinput.txt",'r'); + +ip = int(f.readline()[4:]); +instructions = []; + +for l in f: + l = l.split(); + i = []; + i.append(l[0]); + l_i = [int(x) for x in l[1:]]; + i += l_i; + instructions.append(i); + + +part1 = None; +instr_lim = len(instructions); + +while True: + reg = [0,0,0,0,0,0]; + reg[ip] = 0; + part1 = None; + + while True: + if( reg[ip] >= instr_lim): break; + if( reg[ip] >= 28): + #print(reg); + part1 = int(reg[5]); + break; + instr = instructions[reg[ip]]; + fun = MatchFunctions[instr[0]]; + reg = fun(reg,instr); + reg[ip] += 1; + + if part1 != None: + break; + +print("part 1 = ", part1, "(bruteforce)"); + +#part2 + +store1 = set(); #var ehich determines if the loop continues +store5 = set(); #var checked against the reg0 +part1 = None; +part2 = None; +p2 = None; + +reg5 = 10678677; +reg1 = 2**16; +while True: + reg4 = reg1%256; + reg5 += reg4; + reg5 = ((reg5%2**24)*65899)%(2**24); + + if reg1<256: + if not store5: + part1 = int(reg5); + if reg5 not in store5: + part2 = int(reg5); + store5.add(reg5); + #print("regs", reg1, reg5) + reg1 = reg5 | 2**16; + if reg1 in store1: + break; + store1.add(reg1); + reg5 = 10678677; + continue; + + reg1 = reg1//256; + +print("part 1 = ", part1); +print("part 2 = ", part2); + +#my input +''' +#ip 2 +seti 123 0 5 +bani 5 456 5 +eqri 5 72 5 +addr 5 2 2 +seti 0 0 2 +seti 0 4 5 +bori 5 65536 1 +seti 10678677 3 5 +bani 1 255 4 +addr 5 4 5 +bani 5 16777215 5 +muli 5 65899 5 +bani 5 16777215 5 +gtir 256 1 4 +addr 4 2 2 +addi 2 1 2 +seti 27 5 2 +seti 0 6 4 +addi 4 1 3 +muli 3 256 3 +gtrr 3 1 3 +addr 3 2 2 +addi 2 1 2 +seti 25 4 2 +addi 4 1 4 +seti 17 6 2 +setr 4 6 1 +seti 7 5 2 +eqrr 5 0 4 +addr 4 2 2 +seti 5 4 2 +''' 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"); + + diff --git a/2018/aoc2018-d23-p1.py b/2018/aoc2018-d23-p1.py new file mode 100644 index 0000000..a387337 --- /dev/null +++ b/2018/aoc2018-d23-p1.py @@ -0,0 +1,47 @@ +#advent of code 2018 +#day 23 +#part 1 and part 2 + + +f = open("input.txt",'r'); +Testing = False; +#Testing = True; +if Testing: + f.close(); + f = open("testinput.txt",'r'); + +def manhattan(pos1, pos2): + mx = abs(pos1[0] - pos2[0]); + my = abs(pos1[1] - pos2[1]); + mz = abs(pos1[2] - pos2[2]); + return mx + my + mz; + +class nanobot: + def __init__(self, pos, r): + self.pos = pos; + self.radius = r; + def __str__(self): + return f'pos={self.pos}, r={self.radius}'; + +bots = []; +for l in f: + l = l.replace("<","("); + l = l.replace(">",")"); + l = l.replace("r=",""); + l = l.replace("pos=",""); + p, r = eval(l); + bots.append(nanobot(p,r)); + +f.close(); + +strongest_radius = 0; +strongest_pos = None; +for bot in bots: + if bot.radius > strongest_radius: + strongest_radius = bot.radius; + strongest_pos = bot.pos; + +part1 = 0; +for bot in bots: + if (manhattan(strongest_pos, bot.pos) <= strongest_radius): part1 += 1; +print("part1 = ", part1); diff --git a/2018/aoc2018-d24.py b/2018/aoc2018-d24.py new file mode 100644 index 0000000..e5b45f5 --- /dev/null +++ b/2018/aoc2018-d24.py @@ -0,0 +1,211 @@ +#advent of code 2018 +#day 24 +#part 1 and part 2 + +#this was very fun to do +#easier version of 2018 day 15 due to no pathfinding +#part 1 no probs, but it took me way more time to finish part 2 than it should have +#at first I didn't account for a possible stalemate, but it was late at night +#(I read the input and didn't see any stalemate scenarios based on weaknesses +#but now that I think about it I was reading the testinput from the example lol) +#so I was trying to debug whats wrong with my current code +#instead of adding the few lines to resolve the stalemates +#the groups were simply refusing to fight +#but to be fair, there definitely were some bugs in a previous version, +#because I still wasn't getting the answer even when milk boost value was getting huge + +import copy; #library required for the deepcopy function + +f = open("input.txt",'r'); +Testing = False; +#Testing = True; +if Testing: + f.close(); + f = open("testinput.txt",'r'); + +def readinput(group): + if group.find("(") != -1: + immu_weak = group[group.find("("):group.find(")")+1]; + group = group.replace(immu_weak, ""); + immu_weak = immu_weak.replace("(",""); + immu_weak = immu_weak.replace(")",""); + immu_weak = immu_weak.replace(", ",","); + if immu_weak.find(";") != -1: + if (immu_weak[0] == "i"): + i_i = 0; + w_i = 1; + elif (immu_weak[0] == "w"): + i_i = 1; + w_i = 0; + immu_weak = immu_weak.replace("weak to ",""); + immu_weak = immu_weak.replace("immune to ",""); + immu_weak = immu_weak.replace(" ",""); + iw = immu_weak.split(";"); + immu = iw[i_i].split(","); + weak = iw[w_i].split(","); + elif immu_weak[0] == "w": + immu_weak = immu_weak.replace("weak to ",""); + immu = []; + weak = immu_weak.split(","); + else: + weak = []; + immu_weak = immu_weak.replace("immune to ",""); + immu = immu_weak.split(","); + else: + immu = []; + weak = []; + group = group.replace("units each with", " "); + group = group.replace("hit points", " "); + group = group.replace("with an attack that does", " "); + group = group.replace("damage at initiative", " "); + g = group.split(); + gr = [int(x) for x in g[:-2]]; + gr.append(g[-2]); + gr.append(int(g[-1])); + gr.append(immu); + gr.append(weak); + return gr; + + +class army: + def __init__(self, side, ID, inputlist): + self.side = side; + self.id = ID; + self.units = inputlist[0]; + self.hp = inputlist[1]; + self.dmgVal = inputlist[2]; + self.dmgType = inputlist[3]; + self.initiative = inputlist[4]; + self.immune = inputlist[5]; + self.weak = inputlist[6]; + self.IsDead = False; + self.IsTargeted = False; + self.Target = None; + def __str__(self): + return f'<{self.side}>: {self.units} units each with {self.hp} hit points (immune to {self.immune}; weak to {self.weak}) with an attack that does {self.dmgVal} {self.dmgType} damage at initiative {self.initiative}'; + + def EffectivePower(self,TargetWeak,TargetImmune): + DamageDealt = self.units*self.dmgVal; + if self.dmgType in TargetWeak: + DamageDealt = DamageDealt*2; + elif self.dmgType in TargetImmune: + DamageDealt = 0; + return DamageDealt; + + def TakeDamage(self, DamageReceived): + UnitsKilled = DamageReceived//self.hp; + self.units -= UnitsKilled; + if self.units <= 0: + self.IsDead = True; + self.units = 0; + +############ +#parse input +groups = {}; +side = "imm"; +currentid = 11; +f.readline(); +for l in f: + if(l=="\n"): break; + groups.update({currentid:army(side,currentid,readinput(l))}); + currentid += 1; +f.readline(); +side = "inf"; +for l in f: + if(l=="\n"): break; + groups.update({currentid:army(side,currentid,readinput(l))}); + currentid += 1; +f.close(); + +immsys = 0; +infect = 0; +for g in groups: + if groups[g].side == "imm": immsys +=1; + if groups[g].side == "inf": infect +=1; + +milk = 0; +groupcopy = copy.deepcopy(groups); #deepcopy of groups to reset them + +winner = "inf"; #just to initiate the algo + +#keep doing battles until immune system wins +while winner == "inf": + groups = copy.deepcopy(groupcopy); + #count the initial number of groups for immune system and infection + #apply milk booster to immune system + immsys = 0; + infect = 0; + for g in groups: + if groups[g].side == "imm": immsys +=1; + if groups[g].side == "inf": infect +=1; + if groups[g].side == "imm": groups[g].dmgVal += milk; + groups[g].IsDead = False; + + #battle algorythm + while immsys > 0 and infect > 0: + kills = 0; + UnitsBefore = 0; + for g in groups: + UnitsBefore += groups[g].units; + #phase 1 target selection + TSqueue = [g for g in groups if groups[g].IsDead == False]; + TSqueue = sorted(TSqueue, key=lambda g: (groups[g].EffectivePower([],[]),groups[g].initiative), reverse = True); + for a in TSqueue: + groups[a].Target = None; + PossibleTargets = [g for g in groups if groups[g].IsDead == False and groups[g].side != groups[a].side]; + PossibleTargets = sorted(PossibleTargets, key=lambda g: (groups[a].EffectivePower(groups[g].weak,groups[g].immune),groups[g].EffectivePower([],[]),groups[g].initiative), reverse=True); + for t in PossibleTargets: + if groups[t].IsTargeted == False and groups[a].EffectivePower(groups[t].weak,groups[t].immune) > 0: + groups[a].Target = int(t); + groups[t].IsTargeted = True; + break; + + #phase 2 attacking + ATqueue = TSqueue.copy(); + ATqueue = sorted(ATqueue, key=lambda g: groups[g].initiative, reverse = True); + for a in ATqueue: + if groups[a].IsDead or groups[a].Target == None: continue; + t = groups[a].Target; + groups[t].TakeDamage( groups[a].EffectivePower(groups[t].weak,groups[t].immune) ); + + #reset the IsTargeted parameter for the next iteration of the battle + #count the groups of each side and calculate kills + immsys = 0; + infect = 0; + UnitsAfter = 0; + for g in groups: + groups[g].IsTargeted = False; + if groups[g].side == "imm" and groups[g].IsDead == False: immsys +=1; + if groups[g].side == "inf" and groups[g].IsDead == False: infect +=1; + UnitsAfter += groups[g].units; + + #if there were no kills in the current iteration of battle - finish the battle + kills = UnitsBefore - UnitsAfter; + if kills == 0: + break; + + #declare the winner (infection wins in case of a stalemate) + if kills == 0: + winner = "inf"; + elif immsys == 0: + winner = "inf"; + else: + winner = "imm"; + + #calculate part 1 + if milk == 0: + part1 = 0; + for g in groups: + if groups[g].side == winner and groups[g].IsDead == False: + part1 += groups[g].units; + + milk += 1; #raise milk booster for upcoming new battle + +#calculate part 2 +part2 = 0; +for g in groups: + if groups[g].side == winner and groups[g].IsDead == False: + part2 += groups[g].units; + +print("part 1 = ", part1); +print("part 2 = ", part2); diff --git a/2018/aoc2018-d25.py b/2018/aoc2018-d25.py new file mode 100644 index 0000000..37367df --- /dev/null +++ b/2018/aoc2018-d25.py @@ -0,0 +1,69 @@ +#advent of code 2018 +#day 25 +#part 1 + +#at first seemed really easy, then it got a bit more complex +#but then again it seemed easy again +#it just required a bit more thought to put into +#could probably be bit more optimized, especially with Constellations variable +#which could be a simple counter instead of a list, but w/e + +f = open("input.txt",'r'); +Testing = False; +#Testing = True; +if Testing: + f.close(); + f = open("testinput.txt",'r'); + +class spacepoint: + def __init__( self, coords ): + self.Coords = coords; + self.Adjacent = []; + self.Visited = False; + + def manh_dist(self, OtherPoint): #calculate manhattan distance from self to OtherPoint + m = 0; + m += abs(self.Coords[0] - OtherPoint[0]); + m += abs(self.Coords[1] - OtherPoint[1]); + m += abs(self.Coords[2] - OtherPoint[2]); + m += abs(self.Coords[3] - OtherPoint[3]); + return m; + #add all points from the grid that are within range to the "Adjacent" property + def get_adjacent(self, grid): + for g in grid: + if self == grid[g]: continue; + if (self.manh_dist(grid[g].Coords) <= 3): + self.Adjacent.append(g); + #recursive function(method) to make a list of all points that are in the same constellation + #"Visited" property prevents an infinite loop + def Find_Subset(self, grid, subset): + self.Visited = True; + subset.extend(self.Adjacent); + for n in self.Adjacent: + if grid[n].Visited: continue; + subset = grid[n].Find_Subset(grid, subset); + return subset; + + def __str__(self): + return f'{self.Coords}'; + +#parse input into the program +FixedPoints = {}; +for l in f: + l = "(" + l + ")"; + p = eval(l); + FixedPoints.update({p:spacepoint(p)}); +f.close(); + +#calculate neighbouring points for each point from input +for p in FixedPoints: + FixedPoints[p].get_adjacent(FixedPoints); + +#find all constellations +Constellations = []; +for p in FixedPoints: + if FixedPoints[p].Visited: continue; + Constellations.append(len(set(FixedPoints[p].Find_Subset(FixedPoints, [p])))); + +part1 = len(Constellations); +print("part 1 = ", part1); |