diff options
author | blenovo <bk@gmail.com> | 2024-12-15 21:35:41 +0100 |
---|---|---|
committer | blenovo <bk@gmail.com> | 2024-12-15 21:35:41 +0100 |
commit | b96161d57f868e32792b8af631fa76edd89b543c (patch) | |
tree | 9b6dca211de153ceabec282e3269cea2190adde5 | |
parent | b00dd9290d19d4e670b2fff3d9f1b29cdd5aa59e (diff) |
finally restored git access so adding 2024, days 01-15
-rw-r--r-- | 2024/aoc2024-d02.py | 41 | ||||
-rw-r--r-- | 2024/aoc2024-d03.py | 36 | ||||
-rw-r--r-- | 2024/aoc2024-d04.py | 62 | ||||
-rw-r--r-- | 2024/aoc2024-d05.py | 57 | ||||
-rw-r--r-- | 2024/aoc2024-d06.py | 63 | ||||
-rw-r--r-- | 2024/aoc2024-d07.py | 40 | ||||
-rw-r--r-- | 2024/aoc2024-d08.py | 41 | ||||
-rw-r--r-- | 2024/aoc2024-d09-1.py | 56 | ||||
-rw-r--r-- | 2024/aoc2024-d09-2.py | 41 | ||||
-rw-r--r-- | 2024/aoc2024-d10.py | 55 | ||||
-rw-r--r-- | 2024/aoc2024-d11.py | 50 | ||||
-rw-r--r-- | 2024/aoc2024-d12.py | 83 | ||||
-rw-r--r-- | 2024/aoc2024-d13.py | 31 | ||||
-rw-r--r-- | 2024/aoc2024-d14.py | 87 | ||||
-rw-r--r-- | 2024/aoc2024-d15.py | 157 |
15 files changed, 900 insertions, 0 deletions
diff --git a/2024/aoc2024-d02.py b/2024/aoc2024-d02.py new file mode 100644 index 0000000..655fdc1 --- /dev/null +++ b/2024/aoc2024-d02.py @@ -0,0 +1,41 @@ +#advent of code 2024 +#day 02 +part1 = 0; +ToCheck = []; + +f = open("02.in","r"); +for l in f: + levels = [int(lvl) for lvl in l.split()]; + if (levels[1]-levels[0]) < 0: + levels.reverse(); + dMin = 4; + dMax = 0; + for i in range(1,len(levels)): + d = levels[i] - levels[i-1]; + dMin = min(dMin,d); + dMax = max(dMax,d); + if dMax <=3 and dMin >= 1: + part1 += 1; + else: + ToCheck.append(levels); +f.close(); + +correction = 0; +for levels in ToCheck: + isOK = False; + for x in range(len(levels)): + NewLevels = [levels[y] for y in range(len(levels)) if y !=x] + for _ in ["n","r"]: #normal and reversed, reverse and the end of first iteration + dMin = 4; + dMax = 0; + for i in range(1,len(NewLevels)): + d = NewLevels[i] - NewLevels[i-1]; + dMin = min(dMin,d); + dMax = max(dMax,d); + if dMax <=3 and dMin >= 1: isOK = True; + NewLevels.reverse(); + if isOK: correction += 1; + +part2 = part1 + correction; +print("part 1 = ", part1); +print("part 2 = ", part2); diff --git a/2024/aoc2024-d03.py b/2024/aoc2024-d03.py new file mode 100644 index 0000000..ed1bd2b --- /dev/null +++ b/2024/aoc2024-d03.py @@ -0,0 +1,36 @@ +#advent of code 2024 +#day 03 +#could be improved to match only numbers of lengths 1-3 +#instead of if statements in mul function +import re + +def mul(foundmatch): + foundmatch = foundmatch[4:-1]; + v1, v2 = [int(val) for val in foundmatch.split(",")]; + if v1 > 999: v1 = 0; + if v2 > 999: v2 = 0; + return v1*v2; + +part1 = 0; +part2 = 0; +instr = []; + +f = open("03.in","r") +for line in f: + regmatch = r'(mul\(\d+,\d+\))|(don\'t\(\))|(do\(\))'; + for m in re.findall(regmatch,line): + instr.append(m); +f.close(); + +ok = True; +for i in instr: + m, dn, do = i; #mul() / dont() / do() + if dn: ok = False; + elif do: ok = True; + if m: + v = mul(m); + part1 += v; + part2 += v*ok; + +print("part 1 =", part1); +print("part 2 = ", part2); diff --git a/2024/aoc2024-d04.py b/2024/aoc2024-d04.py new file mode 100644 index 0000000..c999c83 --- /dev/null +++ b/2024/aoc2024-d04.py @@ -0,0 +1,62 @@ +#advent of code 2024 +#day 04 +#the issue with 1st version of part 2 was that I didn't account for +#a cross of words "MAM" and "SAS" +#counting the letters gave a false positive (number of "m" and "s" was the same +#but it wasn't a cross of 2 "MAS" words +#now it check independently each word (dirs1 and dirs2) + +part1 = 0; +part2 = 0; +word = "XMAS"; +dirs = ((1,0),(-1,0),(0,-1),(0,1),(-1,-1),(-1,1),(1,1),(1,-1)); +Alist = []; +grid = {}; +xm = 0; +ym = 0; + +f = open("04.in","r") +for y,l in enumerate(f): + ym = y +1; + l = l[:-1]; + for x,c in enumerate(l): + xm = x +1; + grid[(x,y)] = c; +f.close(); + +for y in range(ym): + for x in range(xm): + if grid[(x,y)] == "A": Alist.append((x,y)); + for d in dirs: + dx, dy = d; + IsWord = True; + for i in range(len(word)): + nx = x + dx*i; + ny = y + dy*i; + nc = grid.get((nx,ny),"q"); + if nc != word[i]: + IsWord = False; + break; + if IsWord: part1 += 1; + +dirs1 = ((-1,-1),(1,1)); +dirs2 = ((-1,1),(1,-1)); +dirs0 = [dirs1,dirs2]; +for A in Alist: + x,y = A; + xcount = 0; + for d0 in dirs0: + cross = {}; + for d in d0: + dx,dy = d; + nx = x + dx; + ny = y + dy; + c = grid.get((nx,ny),"q"); + v = cross.get(c,0); + cross[c] = v + 1; + if cross.get("S",0) == 1 and cross.get("M",0) ==1: + xcount += 1; + if xcount == 2: part2 += 1; + +print("part 1 =", part1); +print("part 2 =", part2); diff --git a/2024/aoc2024-d05.py b/2024/aoc2024-d05.py new file mode 100644 index 0000000..2f8490a --- /dev/null +++ b/2024/aoc2024-d05.py @@ -0,0 +1,57 @@ +#advent of code 2024 +#day 05 +#cool, but seemed harder on first glance +#part1: keep looking up the rules for each page in an update +#if any of the pages in rules are present before the current page +#then the update is incorrect, else add the middle page to part1 score +#part2: keep correcting the order until each rule is satisfied +#popping the incorrectly placed page, then inserting it at the index +#of the page violating the rule +#edit: apparently there's a cycle in the rules which may lead to inifite +#corrections, but in my solution, or at least for my input, that isnt the case + +f = open("05.in","r"); +r, u = f.read().split("\n\n"); #rules and updates +f.close(); +rules, updates = {}, []; +part1 = 0; +part2 = 0; + +for l in r.split("\n"): + v1, v2 = [int(x) for x in l.split("|")]; + if not v1 in rules: rules[v1] = []; + rules[v1].append(v2); + +for l in u.split("\n")[:-1]: #skipping last line because its reading an empty line + updates.append([int(x) for x in l.split(",")]); + +def Verify(update): + ok = True; + for x, page in enumerate(update): + if page in rules: + for rule in rules[page]: + if rule in update[0:x]: ok = False; + return ok*update[len(update)//2]; + +def correction(update): + NewUpdate = update.copy(); + ok = False; + while not ok: + ok = True; + for x, page in enumerate(NewUpdate): + if page in rules: + for y in range(x,-1,-1): + if NewUpdate[y] in rules[page]: + ok = False; + NewUpdate.pop(x); + NewUpdate.insert(y, page); + break; + return NewUpdate[len(NewUpdate)//2]; + +for update in updates: + score = Verify(update); + part1 += score; + if score == 0: part2 += correction(update); + +print("part 1 =", part1); +print("part 2 =", part2); diff --git a/2024/aoc2024-d06.py b/2024/aoc2024-d06.py new file mode 100644 index 0000000..e5da9b4 --- /dev/null +++ b/2024/aoc2024-d06.py @@ -0,0 +1,63 @@ +#advent of code 2024 +#day 06 +#cleaned up + + +grid = {}; +xMin, yMin, xMax, yMax = 0, 0, 0, 0; +dirs = [(0,-1),(1,0),(0,1),(-1,0)]; +d = 0; #starting direction of the guard + +f = open("06.in","r"); +for y, l in enumerate(f): + yMax = max(yMax,y); + for x, c in enumerate(l[:-1]): + xMax = max(xMax,x); + if c == "^": + guard1 = (x,y); #guard position + guard2 = (x,y); #guard pos for part 2 + c = "."; + grid[(x,y)] = c; +f.close(); + +visited = set(); +while True: + gx, gy = guard1; #split guard coordinates + dx, dy = dirs[d]; + if not (xMin <= gx <= xMax)*(yMin <= gy <= yMax): break; + if grid.get((gx+dx,gy+dy),".") == "#": + d = (d+1)%4; + dx, dy = dirs[d]; + visited.add(guard1); + guard1 = (gx+dx,gy+dy); + +#definition of loop: if the guard meets the same obstacle twice, then he's looping +def bruteforce(NewPos,guard): #check if inserting in NewPos position an obstacle will create a loop + d = 0; + obstacles = set(); + #register in set "obstacles" encountered obstacles in format: x,y,d, + #where d is the direction from which guard encountered the obstacle + ok = True + while True: + gx, gy = guard; + dx, dy = dirs[d]; + if not (xMin <= gx+dy <= xMax)*(yMin <= gy+dy <= yMax): + ok = False; #if the guard left the grid then the new obstacle didnt work + break; + if grid.get((gx+dx,gy+dy),".") == "#" or (gx+dx,gy+dy)==NewPos: + d = (d+1)%4; + dx, dy = dirs[d]; + if (guard,d) in obstacles: break; + obstacles.add((guard,d)); + else: + guard = (gx+dx,gy+dy); + return ok; + +NewPlaces = set(); #store the correct possibilites in a set just in case to prevent duplicates +for v in visited: #simulate another guard patrol for each possible new position v + if bruteforce(v,guard2): NewPlaces.add(v); #if he loops, +1 to score + +part1 = len(visited); +part2 = len(NewPlaces); +print("part 1 =", part1); +print("part 2 =", part2); diff --git a/2024/aoc2024-d07.py b/2024/aoc2024-d07.py new file mode 100644 index 0000000..46a594d --- /dev/null +++ b/2024/aoc2024-d07.py @@ -0,0 +1,40 @@ +#advent of code 2024 +#day 07 +#rewritten to do both parts at the same time +#very slow solution (roughly 10 mins of runtime) +#it generates all possible arrangements of operators +#to fill in the empty spaces in the equation +#on top of that, it utilizes eval() +#however, the solution works and that's the important part + +import itertools + +def calculate(test,nums,ops): + N = [n for n in nums.split(" ")]; + ValidAnswerP1 = False; + ValidAnswerP2 = False; + for prod in itertools.product(ops,repeat=nums.count(" ")): + score = ''+N[0]; + for i,p in enumerate(prod): + if p == "||": p = ""; + teststring = str(score) + p + N[i+1]; + score = eval(teststring); + if score == test: + ValidAnswerP2 = True; + if "||" not in prod: ValidAnswerP1 = True; + if ValidAnswerP1 and ValidAnswerP2: break; + return ValidAnswerP1, ValidAnswerP2; + +part1 = 0; +part2 = 0; +ops = ["+","*","||"]; +f = open("07.ex","r"); +for l in f: + TestValue, numbers = l[:-1].split(": "); + TestValue = int(TestValue); + valid_p1, valid_p2 = calculate(TestValue, numbers,ops); + part1 += valid_p1*TestValue; + part2 += valid_p2*TestValue; +f.close(); +print("part 1 = ", part1); +print("part 2 = ", part2); diff --git a/2024/aoc2024-d08.py b/2024/aoc2024-d08.py new file mode 100644 index 0000000..b977a26 --- /dev/null +++ b/2024/aoc2024-d08.py @@ -0,0 +1,41 @@ +#advent of code 2024 +#day 08 +#cool +xMin, yMin, xMax, yMax = 0,0,0,0; #map bounds +antennas = {}; #dictionary of antennas +antinodes1 = set(); #antinodes for part 1 +antinodes2 = set(); #anitnodes for part 2 + +f = open("08.in","r"); +for y,l in enumerate(f): + yMax = max(yMax,y); + for x, c in enumerate(l[:-1]): + xMax = max(xMax,x); + if c != ".": + if antennas.get(c,None) == None: + antennas[c] = []; + antennas[c].append((x,y)); +f.close(); + +for antenna in antennas: #for each antenna + for a1 in antennas[antenna]: #for each instance of that antenna + for a2 in antennas[antenna]: #for each counterpart of that instance + if a1 == a2: continue; #antenna can't have the antinode on its own + a1x, a1y = a1; + a2x, a2y = a2; + dx = a2x-a1x; + dy = a2y-a1y; + if (xMin <= a1x-dx <= xMax) and (yMin <= a1y-dy <= yMax): + antinodes1.add((a1x-dx,a1y-dy)); + antinodes2.add((a1x,a1y)); + while True: + ax = a1x-dx; + ay = a1y-dy; + if not ((xMin <= ax <= xMax) and (yMin <= ay <= yMax)): + break; + antinodes2.add((ax,ay)); + a1x = ax; + a1y = ay; + +print("part 1 =", len(antinodes1)); +print("part 2 =", len(antinodes2)); diff --git a/2024/aoc2024-d09-1.py b/2024/aoc2024-d09-1.py new file mode 100644 index 0000000..3a3e1e6 --- /dev/null +++ b/2024/aoc2024-d09-1.py @@ -0,0 +1,56 @@ +#advent of code 2024 +#day 09 - part 1 +#I'll rewrite it to include both parts in the same file one day +#tbh I'm not even reviewing this shit +#very slow part 1, part 2 relatively fast + +part1 = 0; +disk = open("09.in","r").readline().strip(); +files = {}; +freespace = {}; +NewDisk = {}; +fid = 0; +sid = 0; +ind = 0; +for i in range (0,len(disk)): + if i%2 == 0: + files[fid] =[int(x)+ind for x in range(int(disk[i]))]; + ind += int(disk[i]); + fid += 1; + else: + freespace[sid] = [int(x)+ind for x in range(int(disk[i]))]; + ind += int(disk[i]); + sid +=1; + +si = 0; #space index +fi = fid -1; #file index +originalSize = len(files[fi]); +while sum([len(freespace[x]) for x in freespace]) > 0: + #empty list of freespaces - change to another one + if len(freespace[si]) == 0: + si += 1; + continue; + + #clear freespace that is after the fileblock with biggest index + FileMax = max([max(files[x]) for x in files]); + if FileMax < freespace[si][-1]: + freespace[si].pop(-1); + continue; + + #switch places between last fileblock and first freespace + files[fi].pop(); + originalSize -= 1; + NewSpace = freespace[si].pop(0); + files[fi].insert(0,NewSpace); + + #if that was the last fileblock, switch to second to last file + if originalSize == 0: + fi -= 1; + originalSize = len(files[fi]); + +#checksum calculation +for file in files: + for f in files[file]: + part1 += file*f; + +print("part 1 =", part1); diff --git a/2024/aoc2024-d09-2.py b/2024/aoc2024-d09-2.py new file mode 100644 index 0000000..483388e --- /dev/null +++ b/2024/aoc2024-d09-2.py @@ -0,0 +1,41 @@ +#advent of code 2024 +#day 09 - part 2 +#I'll rewrite it to include both parts in the same file one day +#tbh I'm not even reviewing this shit +#very slow part 1, part 2 relatively fast + +part2 = 0; +disk = open("09.in","r").readline().strip(); +files = {}; +freespace = {}; +NewDisk = {}; +fid = 0; +sid = 0; +ind = 0; +for i in range (0,len(disk)): + if i%2 == 0: + files[fid] =[int(x)+ind for x in range(int(disk[i]))]; + ind += int(disk[i]); + fid += 1; + else: + freespace[sid] = [int(x)+ind for x in range(int(disk[i]))]; + ind += int(disk[i]); + sid +=1; + +for fi in range(fid-1,-1,-1): #file index, from last to first + CurrentLimit = min(files[fi]); + for si in range(0, len(freespace)): #space index, from first to last + if len(freespace[si]) <= 0: continue; + if max(freespace[si]) > CurrentLimit: break; + if len(freespace[si]) >= len(files[fi]): + OriginalSize = 0 + len(files[fi]); + for o in range(OriginalSize): + files[fi].pop(); + NewSpace = freespace[si].pop(0); + files[fi].insert(0,NewSpace); + break; + +for file in files: + for f in files[file]: + part2 += file*f; +print("part 2 =", part2); diff --git a/2024/aoc2024-d10.py b/2024/aoc2024-d10.py new file mode 100644 index 0000000..4f51a0d --- /dev/null +++ b/2024/aoc2024-d10.py @@ -0,0 +1,55 @@ +#advent of code 2024 +#day 10 +#tbh I don't think I even understood the task correctly +#but the first idea for part 2 +#that came up to my head worked so i'll take it +#all I did was switch a set into a list +#rewritten so the code does both parts now + +grid = {}; +xMin, yMin, xMax, yMax = 0,0,0,0; +dirs = [(1,0),(-1,0),(0,1),(0,-1)]; +starts = []; + +f = open("10.in","r"); +for y,l in enumerate(f): + yMax = max(y,yMax); + for x,c in enumerate(l.strip()): + grid[(x,y)]=int(c); + if c == "0": starts.append((x,y)); + xMax = max(xMax,x); +f.close(); + +def getAdjacent(pos,h): + adj = []; + xp, yp = pos; + for i,d in enumerate(dirs): + dx,dy = d; + if grid.get((xp+dx,yp+dy),-1)-h == 1: + adj.append((xp+dx,yp+dy,i)); + return adj; + +def pathfinding(starts): + score1 = 0; + score2 = 0; + for start in starts: + subscore1 = set(); + subscore2 = list(); + x0,y0 = start; + queue = [(x0,y0,0)]; + while queue: + px,py,pd = queue.pop(); + CurrentHeight = grid.get((px,py),-1); + if CurrentHeight == 9: + subscore1.add((px,py)); + subscore2.append((px,py,pd)); + else: + NextValid = getAdjacent((px,py),CurrentHeight); + queue.extend(NextValid); + score1 += len(subscore1); + score2 += len(subscore2); + return score1, score2; + +part1, part2 = pathfinding(starts); +print("part 1 =", part1); +print("part 2 =", part2); diff --git a/2024/aoc2024-d11.py b/2024/aoc2024-d11.py new file mode 100644 index 0000000..0d6fea4 --- /dev/null +++ b/2024/aoc2024-d11.py @@ -0,0 +1,50 @@ +#advent of code 2024 +#day 11 +#I liked it. what I did was: +#store current stones in dictionary in format stonenumber:amount of these stones +#iterate the changes in time +#if the given stone is split up for the first time, the result is memorized +#in the changes dictionary +#during iteration, the UpdateStones function tries to read the results of splitting +#if there are none, the stone is getting split up for the first time +#repeat for given steps +stones = {}; +changes = {}; +f = open("11.in","r"); +for s in f.readline().strip().split(" "): + si = int(s); + if si not in stones: stones[si] = 0; + stones[si] += 1; +f.close(); + +def SplitStones(stone): + NewStones = []; + if stone == 0: + NewStones.append(1); + elif len(str(stone))%2==0: + stonestring = str(stone); + sm = len(stonestring)//2; + s1,s2 = stonestring[0:sm], stonestring[sm:]; + NewStones.append(int(s1)); + NewStones.append(int(s2)); + else: + NewStones.append(stone*2024); + return NewStones; + +def UpdateStones(prevStones): + Updated = {}; + for s in prevStones: + if s not in changes: changes[s] = SplitStones(s); + for c in changes[s]: + if c not in Updated: Updated[c] = 0; + Updated[c] += prevStones[s]; + return Updated; + +steps1 = 25; #for part 1 +steps2 = 75; #for part 2 +for step in range(1,steps2+1): + stones = UpdateStones(stones); + if step == steps1: part1 = sum([stones[x] for x in stones]); +part2 = sum([stones[x] for x in stones]); +print("part 1 =", part1); +print("part 2 =", part2); diff --git a/2024/aoc2024-d12.py b/2024/aoc2024-d12.py new file mode 100644 index 0000000..b7ac378 --- /dev/null +++ b/2024/aoc2024-d12.py @@ -0,0 +1,83 @@ +#advent of code 2024 +#day 12 +#we needed area of the plot and its perimiter +#we floodfill given plot and store the coordinates of the plants +#then, on the borders, we register the coordinates of the plants +#that are adjacent to our plot and the direction from which it was +#registered during floodfill. the length of this set (perim) is the perimiter +#then, for part 2, we group the points in perim set by their direction +#(3rd parameter), then we sort each group based on the coordinate +#that is changing, that is: if the fence was registered from the north, +#then it is part of the north-facing fence, so we sort it by the x-axis +#identical for southern and similar for east/west (y-axis) +#iterate over every fence point, if the gap is greater 1 then increment +#the amount of sides. I probably should make a drawing because +#I'll forget what it does in the future despite writing this comment +grid = {}; +f = open("12.in","r"); +for y,l in enumerate(f): + for x,c in enumerate(l.strip()): + grid[(x,y)]=c; +f.close(); + +dirs = [(1,0),(0,1),(-1,0),(0,-1)]; +visited = set(); + +def getAdjacent(pos,l): + adj = []; + vis = set(); + xp, yp = pos; + for di,d in enumerate(dirs): + dx,dy = d; + if grid.get((xp+dx,yp+dy),".") == l: + adj.append((xp+dx,yp+dy)); + else: + vis.add((xp+dx,yp+dy,di)); + return adj, vis; + +def sides(perimiter): + SidesTotal = 0; + NumberOfSides = {}; + PerimsGrouped = {}; + for di in range(4): + NumberOfSides[di] = 0; + PerimsGrouped[di] = [m for m in perimiter if m[2]==di]; + PerimsGrouped[di] = sorted(PerimsGrouped[di], key=lambda m: (m[di%2],m[(di+1)%2])); + if len(PerimsGrouped[di]) == 0: + continue; + else: + NumberOfSides[di] += 1; + prevx,prevy,prevd = PerimsGrouped[di][0]; + for PerPos in PerimsGrouped[di][1:]: + x,y,d = PerPos; + if not(abs(x-prevx)==di%2 and abs(y-prevy) ==(di+1)%2): + NumberOfSides[di] += 1; + prevx,prevy,prevd = x,y,d; + SidesTotal += NumberOfSides[di]; + return SidesTotal; + +def fill(startpos, letter): + queue = [startpos]; + perim = set(); #add points that aren't part of the area but are adjacent + v = set(); #visited points, that is: the area + while queue: + px,py = queue.pop(); + if (px,py) in v: continue; + visited.add((px,py)); + v.add((px,py)); + NextValid, NextBorders = getAdjacent((px,py),letter); + queue.extend(NextValid); + perim = perim.union(NextBorders); + return len(v), len(perim), sides(perim); + +part1 = 0; +part2 = 0; +for g in grid: + if g in visited:continue; + L = grid[g]; + LandArea, LandPerim, LandSides = fill(g,L); + part1 += LandArea*LandPerim; + part2 += LandArea*LandSides; + +print("part 1 =", part1); +print("part 2 =", part2); diff --git a/2024/aoc2024-d13.py b/2024/aoc2024-d13.py new file mode 100644 index 0000000..8eaff25 --- /dev/null +++ b/2024/aoc2024-d13.py @@ -0,0 +1,31 @@ +#advent of code 2024 +#day 13 +#similar problem was last year +#rewritten to include both parts, could get rid of second for loop but w/e +import re + +def formula(machinevalues,p2): + xa,ya,xb,yb,XP,YP = machinevalues; + XP += p2; + YP += p2; + pusha = (yb*XP - xb*YP)/(yb*xa - xb*ya); #number of pushes - button A + pushb = (XP - xa*pusha)/xb; #number of pushes - button B + Valid = (pusha%1 == 0)*(pushb%1==0); #check if solution is an integer + cost = 3*int(pusha) + int(pushb); + return Valid*cost; + +machines = []; +f = open("13.in","r"); +for l in f.read().split("\n\n"): + values = re.findall(r'\d+',l); + machines.append([int(v) for v in values]); +f.close(); + +part1, part2 = 0, 0; +for machine in machines: + part1 += formula(machine,0); + part2 += formula(machine,10000000000000); + +print("part 1 =",part1); +print("part 2 =",part2); + diff --git a/2024/aoc2024-d14.py b/2024/aoc2024-d14.py new file mode 100644 index 0000000..c824d96 --- /dev/null +++ b/2024/aoc2024-d14.py @@ -0,0 +1,87 @@ +#advent of code 2024 +#day 14 +#correctly assumed that the picture will show up +#if the robots don't overlap +#doing previous years with online help paid off +#NOTE: I learned that it's not recommended to +#take modulo of a negative value +#however there was no problem with that in python + +import re + +class robot: + def __init__(self,rx,ry,rvx,rvy): + self.posx = rx; + self.posy = ry; + self.startx = rx; #useless, no need to reset + self.starty = ry; #useless, no need to reset + self.velx = rvx; + self.vely = rvy; + def move(self,steps): + self.posx = (self.posx + steps*self.velx)%xMax; + self.posy = (self.posy + steps*self.vely)%yMax; + def currentpos(self): + return (self.posx,self.posy); + def resetpos(self): #useless, no need to reset + self.posx = self.startx; + self.posy = self.starty; + +robots = []; +xMin = 0; #from part 1 description, grid range +yMin = 0; #from part 1 description, grid range +xMax = 101; #from part 1 description, grid range +yMax = 103; #from part 1 description, grid range + +f = open("14.in","r"); +for l in f: + nums = re.findall(r'-?\d+',l) + robots.append(robot(*[int(n) for n in nums])); +f.close(); + +RobotPositions = {}; +TimeElapsed = 100; #from part 1 description, time of simulation + +for r in robots: + r.move(TimeElapsed); + robx,roby = r.currentpos(); + if RobotPositions.get((robx,roby),None) == None: + RobotPositions[(robx,roby)] = 0; + RobotPositions[(robx,roby)] += 1; + +part1 = 1; #Safety Factor to multiply +qlim = [(0,xMax//2,0,yMax//2),(1+xMax//2,xMax+1,0,yMax//2),(0,xMax//2,1+yMax//2,yMax+1),(1+xMax//2,xMax+1,1+yMax//2,yMax+1)]; +#qlim = ranges of each quadrant in format: x_min,x_max,y_min,y_max +#not to be confused with xMin,xMax,yMin,yMax of the total grid +#could probably change it a bit and check which quadrant given robot +#belongs to after calculating its position at TimeElapsed +#and count them during "r in robots" for loop +for qx1,qx2,qy1,qy2 in qlim: + robotcountq = 0; #number of robots for each quadrant + for y in range(qy1,qy2): + for x in range(qx1,qx2): + robotcountq += RobotPositions.get((x,y),0); + part1 *= robotcountq; + +print("part 1 = ", part1); +TotalRobots = len(robots); +#I assume (in hindsight: correctly) that the picture doesn't show up before the 100s mark from part 1 +#in general robots' positions should be reset and then start the timer from t = 0 +while True: + TimeElapsed += 1; + RobotSpots = set(); + for r in robots: + r.move(1); + robx,roby = r.currentpos(); + RobotSpots.add((robx,roby)); + if len(RobotSpots)==TotalRobots: break; +part2 = TimeElapsed; +print("part 2 = ", part2); +#commented out the print of the tree itself +#for y in range(yMax): +# for x in range(xMax): +# c = "."; +# if (x,y) in RobotSpots: c = "#"; +# #c = RobotPositions.get((x,y),"."); +# print(c,end=""); +# print(); + diff --git a/2024/aoc2024-d15.py b/2024/aoc2024-d15.py new file mode 100644 index 0000000..a6bb5a0 --- /dev/null +++ b/2024/aoc2024-d15.py @@ -0,0 +1,157 @@ +#advent of code 2024 +#day 15 +#could probably reduce the length of code even more +#if I were to index the part2 box +#instead of hardcoding the left and right part of the box +#but w/e, I don't feel like doing it +#the code is ~110 lines of code shorter after cleaning up +#explanation: +#given: picture of the grid (fg) and robots movement list (movement) +#create 2 grids: grid1 and grid2 for part 1 and part 2 respectively +#for part 2 the grid is made wider x2: the grid2 is created with +#resizing dictionary to know what to insert where +#there are 3 functions: GPS, movebox, stackbox +#GPS calculates the result required by puzzle after simulating +#the movement of the robot +#the box variable is a list of characters that are considered a box +#for each part of a puzzle: O for part 1, [ and ] for part 2 +#the thing is that by making it into a list, I don't need +#separate functions for each part: the logic checks if +#the current tile is equal to the last or the first element of this list +#to determine whether or not it's a box +#for list of length 1, element with index 0 and -1 are the same +#next it iterates over each step of the robot's movement in movement list +#before the robot moves, it checks what is in it's way +#if it's a floor ("."), then it takes a step +#if it's a wall ("#"), it stands in the same spot +#if it's a box, it runs the movebox or stackbox function +#to determine if it's viable or not +#stackbox is a function that is run only for part2 if the robot +#wants to push boxes up or down. if it's part 1 or it moves left or right +#it uses movebox function +#movebox function scans the grid in the provided direction +#(accordingly with robots desired movement) as long as it meets +#a wall tile or floor tile. if it's a wall: can't move +#if it's a floor, then for each tile in the path, from last tile to the first +#move each tile in given direction +#stackbox function queues each tile with a box +#and checks if something is above or below (depending on the robots direction) +#if there's another box, it queues up that tile and it's counterpart +#(left or right) to check it again recursively +#if at any moment there's a wall in the path, the robot can't move +#if the final tiles don't have only floor tiles above or below them, +#then each box tile is moved one step in correct direction, +#leaving behind a free space +#this comment is as long as solutions for days 1-13 +#but I just fucking know that I'll look this up one day and will +#not remember anything +grid1 = {}; +grid2 = {}; +dirs = {}; +dirs["^"] = (0,-1); +dirs["v"] = (0,1); +dirs["<"] = (-1,0); +dirs[">"] = (1,0); +resizing = {}; +resizing["#"] = "##"; +resizing["O"] = "[]"; +resizing["."] = ".."; +resizing["@"] = "@."; + +f = open("15.in","r"); +fg, movement = f.read().split("\n\n"); +movement = movement.replace("\n",""); +for y,l in enumerate(fg.split("\n")): + for x,c in enumerate(l.strip()): + if c == "@": + robot1 = (x,y); + robot2 = (2*x,y); + c = "."; + grid1[(x,y)] = c; + for offset in range(2): + grid2[(2*x+offset,y)] = resizing[c][offset]; +f.close(); + +def movebox(BoxPos,direction,grid,box,p2): + valid = True; + bx, by = BoxPos; #coordinates of 1st box to push + dirx,diry = direction; + distance = 1; #python ranges are exclusive on outside + NextTile = grid.get((bx+distance*dirx,by+distance*diry),"#"); + while NextTile == box[0] or NextTile == box[-1]: + distance += 1; + NextTile = grid.get((bx+distance*dirx,by+distance*diry),"#"); + LastTile = grid.get((bx+distance*dirx,by+distance*diry),"#"); + if LastTile == "#": #if wall, can't move + valid = False; + else: #replace each box in the way step by step in reversed order + for dist in range(distance): + NewPosition = (bx+distance*dirx -dist*dirx,by+distance*diry -dist*diry); + PrevPosition = (bx+distance*dirx -dist*dirx -dirx,by + distance*diry - dist*diry - diry); + grid[NewPosition] = grid[PrevPosition]; + grid[PrevPosition] = "."; + return valid; + +def stackbox(BoxPos,direction,grid,box,p2): + valid = True; + bx,by = BoxPos; + dirx,diry = direction; + if grid[BoxPos] == box[0]: BoxPos2 = (bx+1,by,box[-1]); + else: BoxPos2 = (bx-1,by,box[0]); + queue = [(bx,by,grid[BoxPos]),BoxPos2]; #each part of the box is a separate entry in queue + BoxesToMove = queue.copy(); + while queue: + PosToCheck = []; + for queuedpos in queue: + BX,BY,dummy = queuedpos; #dummy not needed for queue loop, needed later + NextTile = grid[(BX,BY+diry)]; + if NextTile == box[0]: + PosToCheck.append((BX,BY+diry,NextTile)); + PosToCheck.append((BX+1,BY+diry,grid[(BX+1,BY+diry)])); + elif NextTile == box[-1]: + PosToCheck.append((BX,BY+diry,NextTile)); + PosToCheck.append((BX-1,BY+diry,grid[(BX-1,BY+diry)])); + elif NextTile == "#": + valid = False; + break; + if valid: + queue = PosToCheck.copy(); + BoxesToMove.extend(PosToCheck); + else: + BoxesToMove = []; + queue = []; + BoxesToMove = sorted(BoxesToMove, key = lambda k: k[1],reverse=diry>0); + for BoxToMove in BoxesToMove: + boxx,boxy,prevtile = BoxToMove; + grid[(boxx,boxy+diry)] = prevtile; + grid[(boxx,boxy)] = "."; + return valid; + +def GPS(grid,robot,p2): + box = ["O"]; + if p2: box = ["[","]"]; + for d in movement: + dx,dy = dirs[d]; + rx,ry = robot; + NextPos = (rx+dx,ry+dy); + tile = grid.get(NextPos,"#"); + if tile == "#": #if wall, dont move + ok = False; + elif tile == box[0] or tile == box[-1]: #if boxes in the way, move boxes first + if p2 and dy != 0: + ok = stackbox(NextPos,dirs[d],grid,box,p2); + else: + ok = movebox(NextPos,dirs[d],grid,box,p2); + else: #if nothing in the way, then move + ok = True; + robot = (rx+ok*dx,ry+ok*dy); + score = 0; + for g in grid: + if grid[g] == box[0]: score += 100*g[1] + g[0]; + return score; + +part1 = GPS(grid1,robot1,False); +part2 = GPS(grid2,robot2,True); +print("part 1 =", part1); +print("part 2 =", part2); + |