summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorblenovo <bk@gmail.com>2024-12-15 21:35:41 +0100
committerblenovo <bk@gmail.com>2024-12-15 21:35:41 +0100
commitb96161d57f868e32792b8af631fa76edd89b543c (patch)
tree9b6dca211de153ceabec282e3269cea2190adde5
parentb00dd9290d19d4e670b2fff3d9f1b29cdd5aa59e (diff)
finally restored git access so adding 2024, days 01-15
-rw-r--r--2024/aoc2024-d02.py41
-rw-r--r--2024/aoc2024-d03.py36
-rw-r--r--2024/aoc2024-d04.py62
-rw-r--r--2024/aoc2024-d05.py57
-rw-r--r--2024/aoc2024-d06.py63
-rw-r--r--2024/aoc2024-d07.py40
-rw-r--r--2024/aoc2024-d08.py41
-rw-r--r--2024/aoc2024-d09-1.py56
-rw-r--r--2024/aoc2024-d09-2.py41
-rw-r--r--2024/aoc2024-d10.py55
-rw-r--r--2024/aoc2024-d11.py50
-rw-r--r--2024/aoc2024-d12.py83
-rw-r--r--2024/aoc2024-d13.py31
-rw-r--r--2024/aoc2024-d14.py87
-rw-r--r--2024/aoc2024-d15.py157
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);
+