summaryrefslogtreecommitdiff
path: root/2024
diff options
context:
space:
mode:
authorblenovo <bk@gmail.com>2025-03-04 15:37:55 +0100
committerblenovo <bk@gmail.com>2025-03-04 15:37:55 +0100
commit15662865f0886209d871a7225bfc62cffd2e0783 (patch)
tree7130fb1b11a058ffdbeefe471eccd6ad461d8843 /2024
parenta926f0a2aa1818879930f5843d5c2cfabd3bfebc (diff)
transfer from previous server
Diffstat (limited to '2024')
-rw-r--r--2024/aoc2024-d01.py37
-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
-rw-r--r--2024/aoc2024-d16-dangerously_unwashed.py170
-rw-r--r--2024/aoc2024-d16-wash_attempt.py102
-rw-r--r--2024/aoc2024-d17.py65
-rw-r--r--2024/aoc2024-d18.py64
-rw-r--r--2024/aoc2024-d19.py49
-rw-r--r--2024/aoc2024-d20.py84
-rw-r--r--2024/aoc2024-d21_hardcoded.py237
-rw-r--r--2024/aoc2024-d22.py79
-rw-r--r--2024/aoc2024-d23.py73
-rw-r--r--2024/aoc2024-d24.py82
-rw-r--r--2024/aoc2024-d25.py55
27 files changed, 1997 insertions, 0 deletions
diff --git a/2024/aoc2024-d01.py b/2024/aoc2024-d01.py
new file mode 100644
index 0000000..034e170
--- /dev/null
+++ b/2024/aoc2024-d01.py
@@ -0,0 +1,37 @@
+#advent of code 2024
+#day 01 p1 & p2
+#heapq was slower than manually sorting the lists
+import time
+t1 = time.time();
+
+part1 = 0;
+part2 = 0;
+ListA = [];
+ListB = [];
+DictA = {};
+
+f = open("01.in");
+for l in f:
+ n1,n2 = l.split(" ");
+ ListA.append(int(n1));
+ ListB.append(int(n2));
+ DictA[int(n1)] = 0;
+f.close();
+
+ListA = sorted(ListA);
+ListB = sorted(ListB);
+
+for i,x1 in enumerate(ListA):
+ x2 = ListB[i];
+ d = abs(x1-x2);
+ part1 += d;
+ if x2 in DictA:
+ DictA[x2] = DictA[x2] + 1;
+
+for x in DictA:
+ part2 += x*DictA[x];
+
+t2 = time.time();
+print("part 1 = ", part1);
+print("part 2 = ", part2);
+print("time ", t2-t1);
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);
+
diff --git a/2024/aoc2024-d16-dangerously_unwashed.py b/2024/aoc2024-d16-dangerously_unwashed.py
new file mode 100644
index 0000000..710a3e5
--- /dev/null
+++ b/2024/aoc2024-d16-dangerously_unwashed.py
@@ -0,0 +1,170 @@
+#advent of code 2024
+#day 16
+#version 1 - not cleaned up
+#don't know anymore, can't explain - keeping the unrevised version because it's hilariously dogshit
+#part 1: simple shortest path search
+#part 2: keep the path searching function running until all paths with distance equal to the shortest path are registered
+#keep track of distance for each node, kinda like djikstra, distance to each node is stored in a dictionary
+#with only exception that it stores all of the possible
+#in short: linked list, in format: node_1:set_of_previous_nodes, previous nodes have shortest possible path to that node
+#keep a map of previous steps, that is Position_now:set of positions that could be used to reach
+#I also commented out a check to prevent going backwards (take step north, then take step south)
+#but I guess that was taken care of during other checks like how big is the distance so it didnt matter
+#the entries in queues with distance greater than registered shortest path are discarded
+#the check is repeated (probably unnecessarily) before queuepush
+#my other issue: there could be more than 1 directions from which I could reach the E
+#while I registered all of them, durign testing I used only one of them
+#turns out there weren't multiple ends (all valid paths reach E from the same direction)
+#sidenote: i think i solved that earlier, because I was getting similar values, but I didn't bother to check
+
+#assumptions:
+#only 1 starting position
+
+import heapq
+
+grid = {};
+xMin, yMin, xMax, yMax = 0,0,0,0; #just for printout
+dirs = [(1,0),(0,1),(-1,0),(0,-1)]; #east, south, west, north
+
+f = open("16.in","r");
+for y,l in enumerate(f):
+ yMax = max(y,yMax);
+ for x,c in enumerate(l.strip()):
+ if c == "S":
+ start = (x,y);
+ c = "."
+ grid[(x,y)]=c;
+ xMax = max(xMax,x);
+f.close();
+
+'''
+for y in range(yMax+1):
+ for x in range(xMax+1):
+ print(grid[(x,y)], end="");
+ print(); #'''
+
+#print(start);
+
+def getAdjacent(coordinates):
+ adj = [];
+ xp, yp, dp = coordinates;
+ for i,d in enumerate(dirs):
+ dx,dy = d;
+ if grid.get((xp+dx,yp+dy),"#") != "#":
+ adj.append((xp+dx,yp+dy,i));
+ return adj;
+
+BigNumber = xMax*yMax*100000000000;
+
+def pathfinding(startingpos):
+ ends = set();
+ visited = set();
+ x0,y0 = startingpos;
+ queue = [];
+ heapq.heappush(queue,(0,(x0,y0,0)));
+ ShortestPath = BigNumber;
+ pathmap = {};
+ pathdis = {};
+ pathmap[(x0,y0,0)] = set();
+
+ while queue:
+ distance, coords = heapq.heappop(queue);
+ #if coords in visited: continue;
+ if distance > ShortestPath: continue;
+ visited.add(coords);
+ px,py,pd = coords;
+
+ #pathmap[(coords,prevs)] = min(distance, pathmap.get((coords,prevs),float('inf')));
+ CurrentTile = grid.get((px,py),"#");
+ if CurrentTile == "E":
+ ShortestPath = min(ShortestPath,distance);
+ if distance == ShortestPath:
+ ends.add(coords);
+
+ #else:
+ NextValid = getAdjacent((px,py,pd));
+ for NV in NextValid:
+ #print("NV", distance+1+1000*(NV[2]!=pd),NV);
+ next_dist = distance+1+1000*(NV[2]!=pd);
+ prev_dist = pathdis.get(NV,BigNumber);
+ if next_dist > prev_dist:
+ continue;
+ if next_dist < prev_dist:
+ pathmap[NV] = set();
+ pathdis[NV] = next_dist;
+ pathmap[NV].add(coords);
+ #if NV[2] == (pd+2)%4: continue;
+ heapq.heappush(queue,(distance+1+1000*(NV[2]!=pd),NV));
+
+ return ShortestPath, pathmap, pathdis, ends;
+
+
+part1, PathMap,PathDis, PathEnds = pathfinding(start);
+part2 = len(PathMap);
+newtotal = set();
+#for shit in tv:
+# shit1,shit2,shit2
+print("part 1 =", part1);
+
+#print(PathMap);
+for pm in PathMap:
+ if len(PathMap[pm]) > 1:
+ print(pm, PathMap[pm]);
+#input();
+
+#print(PathDis);
+#input();
+#print(PathEnds);
+
+def FindSpots(startpos,end,pathmap,pathdis,prevpaths):
+ #sx,sy,_ =
+ validpaths = set();
+ print(end);
+ #for end in endpos:
+ #queue = [end];
+ #scorereversed = 0 + pathdis[end];
+ CurrentPosition = end;
+ validpaths.add(CurrentPosition);
+ #while queue:
+ # x,y,d = queue.pop();
+ if end == startpos:
+ return validpaths;
+ #x,y,d = end;
+ NextPositions = pathmap[end];
+ for np in NextPositions:
+ validpaths.update(FindSpots(startpos,np,pathmap,pathdis,validpaths.copy()));
+
+ return validpaths;
+
+print("FINSPOTS");
+FirstEnd = PathEnds.pop();
+testing = FindSpots(start,FirstEnd,PathMap,PathDis,set());
+print("testing");
+print(testing);
+print(len(testing));
+
+
+
+storeshit = set();
+
+for t in testing:
+ tx,ty,td = t;
+ storeshit.add((tx,ty));
+
+
+for y in range(yMax+1):
+ for x in range(xMax+1):
+ c = grid[(x,y)];
+ #if (x,y) in tv:
+ # c = "O";
+ counter = 0;
+ for ind in range(4):
+ if (x,y,ind) in testing: counter += 1;
+ if counter != 0: c = str(counter);
+ ##c = "O";
+ #break;#'''
+ print(c, end="");
+ print();
+
+print("part 1 =", part1);
+print("part 2 =", len(storeshit));
diff --git a/2024/aoc2024-d16-wash_attempt.py b/2024/aoc2024-d16-wash_attempt.py
new file mode 100644
index 0000000..4d70b48
--- /dev/null
+++ b/2024/aoc2024-d16-wash_attempt.py
@@ -0,0 +1,102 @@
+#advent of code 2024
+#day 16
+#version 2 - slightly cleaned up
+#don't know anymore, can't explain - keeping the unrevised version because it's hilariously dogshit
+#part 1: simple shortest path search
+#part 2: keep the path searching function running until all paths with distance equal to the shortest path are registered
+#keep track of distance for each node, kinda like djikstra, distance to each node is stored in a dictionary
+#with only exception that it stores all of the possible
+#in short: linked list, in format: node_1:set_of_previous_nodes, previous nodes have shortest possible path to that node
+#keep a map of previous steps, that is Position_now:set of positions that could be used to reach
+#I also commented out a check to prevent going backwards (take step north, then take step south)
+#but I guess that was taken care of during other checks like how big is the distance so it didnt matter
+#the entries in queues with distance greater than registered shortest path are discarded
+#the check is repeated (probably unnecessarily) before queuepush
+#my other issue: there could be more than 1 directions from which I could reach the E
+#while I registered all of them, durign testing I used only one of them
+#turns out there weren't multiple ends (all valid paths reach E from the same direction)
+#sidenote: i think i solved that earlier, because I was getting similar values, but I didn't bother to check
+
+#assumptions:
+#only 1 starting position
+#i think the code will work for multiple ends (E) as well, as long as the puzzle wants the shortest path to the closest end
+
+import heapq
+
+grid = {};
+xMin, yMin, xMax, yMax = 0,0,0,0; #could get rid of it but w/e
+dirs = [(1,0),(0,1),(-1,0),(0,-1)]; #east, south, west, north
+
+f = open("16.in","r");
+for y,l in enumerate(f):
+ yMax = max(y,yMax);
+ for x,c in enumerate(l.strip()):
+ if c == "S":
+ start = (x,y,0);
+ c = "."
+ grid[(x,y)]=c;
+ xMax = max(xMax,x);
+f.close();
+
+def getAdjacent(coordinates):
+ adj = [];
+ xp, yp, dp = coordinates;
+ for i,d in enumerate(dirs):
+ dx,dy = d;
+ if grid.get((xp+dx,yp+dy),"#") != "#":
+ adj.append((xp+dx,yp+dy,i));
+ return adj;
+
+def pathfinding(startingpos):
+ ends = set(); #max 4, coordinates of E but coming from each direction
+ BigNumber = xMax*yMax*100000000000; #used to initialize Shortest Path to have something to compare with
+ ShortestPath = BigNumber;
+ pathmap,pathdis = {},{};
+ queue = [];
+ heapq.heappush(queue,(0,startingpos));
+ pathmap[startingpos] = set();
+ while queue:
+ distance, coords = heapq.heappop(queue);
+ if distance > ShortestPath: continue;
+ px,py,pd = coords;
+ if grid.get((px,py),"#") == "E":
+ ShortestPath = min(ShortestPath,distance); #this should be updated only once
+ if distance == ShortestPath:
+ ends.add(coords); #always the same E, just from different direction
+ NextValid = getAdjacent((px,py,pd));
+ for NV in NextValid: #for each valid adjacent node (valid here means it's not a wall)
+ next_dist = distance+1+1000*(NV[2]!=pd);
+ prev_dist = pathdis.get(NV,BigNumber);
+ if next_dist > prev_dist:
+ continue;
+ if next_dist < prev_dist:
+ pathmap[NV] = set(); #reset the set of positions with shortest path to it
+ pathdis[NV] = next_dist; #update the shortest path to node NV
+ pathmap[NV].add(coords);
+ heapq.heappush(queue,(next_dist,NV));
+ return ShortestPath, pathmap, ends;
+
+def FindSpots(startpos,CurrentNode,pathmap):
+ validpaths = set();
+ validpaths.add((CurrentNode[0],CurrentNode[1]));
+ if CurrentNode == startpos:
+ return validpaths;
+ NextPositions = pathmap[CurrentNode]; #read the set of nodes that connected to this node
+ for np in NextPositions:
+ validpaths.update(FindSpots(startpos,np,pathmap));
+ return validpaths;
+
+ValidTiles = set(); #tiles that are on one of the best paths through the maze
+part1, PathMap, PathEnds = pathfinding(start);
+for EndPosition in PathEnds:
+ ValidTiles.update(FindSpots(start,EndPosition,PathMap));
+part2 = len(ValidTiles);
+''' #map print
+for y in range(yMax+1):
+ for x in range(xMax+1):
+ c = grid[(x,y)];
+ if (x,y) in ValidTiles: c = str("O");
+ print(c, end="");
+ print(); #'''
+print("part 1 =", part1);
+print("part 2 =", part2);
diff --git a/2024/aoc2024-d17.py b/2024/aoc2024-d17.py
new file mode 100644
index 0000000..c5a41b1
--- /dev/null
+++ b/2024/aoc2024-d17.py
@@ -0,0 +1,65 @@
+#advent of code 2024
+#day 17
+#code needs input reading, hardcoded my input out of convenience
+#the fucking commas where part of a solution lol, misread the problem there
+#if I were to reverse the logic for 3rd argument in broot function
+#it could work for longer inputs
+import re
+RegsRaw, ProgRaw = open("17.in","r").read().split("\n\n");
+#by Eric's request, the input is removed
+MyCode = [];
+MyA = ;
+
+Register = {};
+Register[0] = 0; #A
+Register[1] = 0; #B
+Register[2] = 0; #C
+
+def combo(op):
+ if op <= 3: return op;
+ elif op == 4: return Register[0];
+ elif op == 5: return Register[1];
+ elif op == 6: return Register[2];
+
+def runprogram(code,A):
+ Results = [];
+ InstrPointer = 0;
+ Register[0] = A; #A
+ while InstrPointer < len(code):
+ operand = code[InstrPointer+1];
+ Inst = code[InstrPointer];
+ #print(InstrPointer, Inst , operand);
+ if Inst == 0: Register[0] = Register[0]>>combo(operand);
+ elif Inst == 1: Register[1] = Register[1]^operand;
+ elif Inst == 2: Register[1] = combo(operand)%8;
+ elif Inst == 3:
+ if Register[0] != 0: InstrPointer = operand -2;
+ elif Inst == 4: Register[1] = Register[1]^Register[2];
+ elif Inst == 5: Results.append(combo(operand)%8);
+ elif Inst == 6: Register[1] = Register[0]>>combo(operand);
+ elif Inst == 7: Register[2] = Register[0]>>combo(operand);
+ InstrPointer += 2;
+ return Results;
+
+def broot(code,PotentialA,ind):
+ valid = [];
+ if ind == 0:
+ PotentialA = [0];
+ for pa in PotentialA:
+ for option in range(8):
+ A_to_check = pow(8,(15-ind))*option + pa;
+ output = runprogram(code,A_to_check);
+ if output[-ind-1:] == code[-ind-1:]: valid.append(A_to_check);
+ if ind != 15:
+ valid = broot(code,valid.copy(),ind+1);
+ return valid;
+
+part1 = ",".join([str(n) for n in runprogram(MyCode,MyA)]);
+print("part 1 =", part1);
+
+MinimalValues = broot(MyCode,[],0);
+part2 = MinimalValues[0];
+for mv in MinimalValues:
+ if runprogram(MyCode,mv) == MyCode: part2 = min(part2,mv);
+
+print("part 2 =",part2);
diff --git a/2024/aoc2024-d18.py b/2024/aoc2024-d18.py
new file mode 100644
index 0000000..5f848fc
--- /dev/null
+++ b/2024/aoc2024-d18.py
@@ -0,0 +1,64 @@
+#advent of code 2024
+#day 18
+#advent of gridbroot
+
+import heapq
+
+mygrid = {};
+FallingBytes = [];
+lim = 70 + 1;
+bytelim = 1024;
+for p in [(X,Y) for Y in range(lim) for X in range(lim)]: mygrid[p] = ".";
+#myfile = "18.ex";
+myfile = "18.in";
+if myfile != "18.in": #if example
+ lim = 7;
+ bytelim = 12;
+
+f = open(myfile,"r");
+for counter,l in enumerate(f):
+ bytex,bytey = [int(v) for v in l.strip().split(",")];
+ FallingBytes.append((bytex,bytey));
+ if counter < bytelim: mygrid[(bytex,bytey)] = "#";
+f.close();
+
+start = (0,0);
+end = (lim-1,lim-1);
+dirs = [(1,0),(0,1),(-1,0),(0,-1)]; #east, south, west, north
+
+def getAdjacent(grid,coordinates):
+ adj = [];
+ xp, yp = coordinates;
+ for i,d in enumerate(dirs):
+ dx,dy = d;
+ if grid.get((xp+dx,yp+dy),"#") != "#":
+ adj.append((xp+dx,yp+dy));
+ return adj;
+
+def shortestpath(grid,startpos):
+ score = 0;
+ visited = set();
+ queue = [(0,startpos)];
+ while queue:
+ dist, pos = heapq.heappop(queue);
+ if pos in visited: continue;
+ visited.add(pos);
+ if pos == end:
+ score = dist;
+ break;
+ NextPositions = getAdjacent(grid,pos);
+ for NP in NextPositions:
+ heapq.heappush(queue,(dist+1,NP));
+ return score
+
+part1 = shortestpath(mygrid,start);
+print("part 1 =",part1);
+part2 = None;
+for b_ind,b in enumerate(FallingBytes[bytelim:]):
+ mygrid[b] = "#";
+ path = shortestpath(mygrid,start);
+ if path == 0:
+ part2 = f'{b[0]},{b[1]}';
+ break;
+
+print("part 2 =",part2);
diff --git a/2024/aoc2024-d19.py b/2024/aoc2024-d19.py
new file mode 100644
index 0000000..eb3721a
--- /dev/null
+++ b/2024/aoc2024-d19.py
@@ -0,0 +1,49 @@
+#advent of code 2024
+#day 19
+#unfortunately I didn't figure it out in time on my own
+#I read some tips on the internet and returned to it on later date
+#my attempt was kinda close but I didn't manage to correctly
+#control the flow of the program which lead to duplicated paths
+#I used the patterns dictionary to split the patterns based on their
+#first letter so I don't need to check all patterns at the same time,
+#only those potentially correct
+#then, using CalcDesign function, we iterate over the whole string
+#the dictionary PatternPaths stores the total amount of possible ways
+#to reach a given substring of the function argument
+#when we check each character c of the argument, we first look up
+#how many paths there are to reach this substring (if it's the first
+#letter then it is hardcoded as 1). Then we iterate over the list of
+#patterns that begin with given character.
+#if the substring plus pattern are the same as the substring of design
+#then it is counted as possible path
+#function returns a bool and an int: if there's at least one way to reach
+#desired design (part 1), and amount of possible ways to reach it (part 2)
+
+def CalcDesign(design):
+ PatternPaths = {};
+ for c_ind,c in enumerate(design): #character index, character
+ subpath = PatternPaths.get(design[:c_ind],0) + 1*(c_ind==0);
+ for pattern in patterns.get(c,[]):
+ subdesign = design[:c_ind] + pattern;
+ if subdesign == design[:c_ind + len(pattern)]:
+ if subdesign not in PatternPaths:
+ PatternPaths[subdesign] = 0;
+ PatternPaths[subdesign] += subpath + 0;
+ paths = PatternPaths.get(design,0);
+ return paths != 0, paths;
+
+part1 = 0;
+part2 = 0;
+patterns = {};
+data1, data2 = open("19.in","r").read().split("\n\n");
+for data in data1.strip().split(", "):
+ if data[0] not in patterns: patterns[data[0]] = [];
+ patterns[data[0]].append(data);
+
+for des in data2.strip().split("\n"):
+ ok,TotalPaths = CalcDesign(des);
+ part1 += ok*1;
+ part2 += TotalPaths;
+
+print("part 1 =", part1);
+print("part 2 =", part2);
diff --git a/2024/aoc2024-d20.py b/2024/aoc2024-d20.py
new file mode 100644
index 0000000..16ed3c4
--- /dev/null
+++ b/2024/aoc2024-d20.py
@@ -0,0 +1,84 @@
+#advent of code 2024
+#day 20
+#shamelessly copypasted previous pathfinding and input parsing
+#except it needed much adjustments because it's not really pathfinding
+#basically: calculate map of distances between each point with djikstra
+#since the path never branches, you can use these distances
+#instead of recalculating the path after each cheat
+#cheat
+
+grid = {};
+xMin, yMin, xMax, yMax = 0,0,0,0; #could get rid of it but w/e
+dirs = [(1,0),(0,1),(-1,0),(0,-1)]; #east, south, west, north
+
+f = open("20.in","r");
+for y,l in enumerate(f):
+ yMax = max(y,yMax);
+ for x,c in enumerate(l.strip()):
+ if c == "S":
+ start = (x,y);
+ c = "."
+ elif c == "E":
+ end = (x,y);
+ c = "."
+ grid[(x,y)]=c;
+ xMax = max(xMax,x);
+f.close();
+
+def getAdjacent(coordinates):
+ adj = [];
+ xp, yp = coordinates;
+ for d in dirs:
+ dx,dy = d;
+ if grid.get((xp+dx,yp+dy),"#") != "#":
+ adj.append((xp+dx,yp+dy));
+ return adj;
+
+def djikstra(startpos,endpos):
+ queue = [];
+ DistanceMap = {};
+ queue = [(startpos,0)];
+ while queue:
+ coords,dist = queue.pop();
+ if coords in DistanceMap: continue;
+ DistanceMap[coords] = min(DistanceMap.get(coords,float('inf')),dist);
+ for NextPos in getAdjacent(coords):
+ queue.append((NextPos,dist+1));
+ return DistanceMap;
+
+def cheatAdjacent(coordinates,secs):
+ adj = set();
+ px,py = coordinates;
+ for X in range(secs+1):
+ for Y in range(secs+1-X):
+ adj.add((px+X*1,py+Y*1));
+ adj.add((px+X*(-1),py+Y*(-1)));
+ adj.add((px+X*(1),py+Y*(-1)));
+ adj.add((px+X*(-1),py+Y*(1)));
+ return adj;
+
+PathDistances = djikstra(start,end);
+part1 = 0;
+part2 = 0;
+for position in PathDistances:
+ d1 = PathDistances.get(position,-1); #negative value if there's no match
+ CheatedSteps = cheatAdjacent(position,20);
+ for ch in CheatedSteps:
+ d2 = PathDistances.get(ch,-1); #negative value if there's no match
+ if d2 == -1: continue; #if the step isn't in the PathDistances, then it's not valid
+ manhattan = abs(ch[0]-position[0]) + abs(ch[1]-position[1])
+ ScoreDifference = d2-d1 - manhattan
+ if ScoreDifference >= 100: #from description, doesn't work for example
+ part2 += 1;
+ part1 += 1*(manhattan <= 2);
+
+print("part 1 =", part1);
+print("part 2 =", part2);
+#grid print for reference/debugging
+#for y in range(yMax+1):
+# for x in range(xMax+1):
+# c = grid[(x,y)];
+# if (x,y) == start: c = "S";
+# elif (x,y) == end: c = "E";
+# print(c, end="");
+# print();
diff --git a/2024/aoc2024-d21_hardcoded.py b/2024/aoc2024-d21_hardcoded.py
new file mode 100644
index 0000000..1d360b2
--- /dev/null
+++ b/2024/aoc2024-d21_hardcoded.py
@@ -0,0 +1,237 @@
+#advent of code 2024
+#day 21
+#finally fucking worked
+#I had a good idea for part1 to not generate all steps but only count
+#the number of steps since the strings for part 2 would eat up all the RAM
+#however I got hit by another problem which I didn't forsee,
+#that is permutations. Basically, for part 2, you need to take into account
+#that maybe going in different order is going to yield better results
+#I hardcoded the possible steps instead of DFSing them like a normal
+#human being and doubled down on it later. When I'll have more time and will to
+#improve the code, I'll include the part where I generate the movements
+#solution for part 1 was developed on my own, but for part 2 I needed
+#external help (at least I understand what's going on in here so it's not
+#that I copy-pasted someone else's code).
+from functools import cache
+import itertools
+#use this for later
+'''
+keyboard_num = {};
+keyboard_num["7"] = (0,0);
+keyboard_num["8"] = (1,0);
+keyboard_num["9"] = (2,0);
+keyboard_num["4"] = (1,0);
+keyboard_num["5"] = (1,1);
+keyboard_num["6"] = (1,2);
+keyboard_num["3"] = (2,0);
+keyboard_num["2"] = (2,1);
+keyboard_num["1"] = (2,2);
+keyboard_num["0"] = (3,1);
+keyboard_num["A"] = (3,2);
+keyboard_dir = {};
+keyboard_dir"^"] = (1,0);
+keyboard_dir"A"] = (2,0);
+keyboard_dir"<"] = (0,1);
+keyboard_dir"v"] = (1,1);
+keyboard_dir">"] = (2,1); #'''
+
+dn = {};
+dn["^"] = {};
+dn["A"] = {};
+dn["<"] = {};
+dn["v"] = {};
+dn[">"] = {};
+dn["^"]["^"] =["A"];
+dn["^"]["A"] =[">A"];
+dn["^"]["<"] =["v<A"];
+dn["^"]["v"] =["vA"];
+dn["^"][">"] =["v>A",">vA"];
+dn["A"]["^"] =["<A"];
+dn["A"]["A"] =["A"];
+dn["A"]["<"] =["v<<A","<v<A"];
+dn["A"]["v"] =["v<A","<vA"];
+dn["A"][">"] =["vA"];
+dn["<"]["^"] =[">^A"];
+dn["<"]["A"] =[">^>A",">>^A"];
+dn["<"]["<"] =["A"];
+dn["<"]["v"] =[">A"];
+dn["<"][">"] =[">>A"];
+dn["v"]["^"] =["^A"];
+dn["v"]["A"] =["^>A",">^A"];
+dn["v"]["<"] =["<A"];
+dn["v"]["v"] =["A"];
+dn["v"][">"] =[">A"];
+dn[">"]["^"] =["^<A","<^A"];
+dn[">"]["A"] =["^A"];
+dn[">"]["<"] =["<<A"];
+dn[">"]["v"] =["<A"];
+dn[">"][">"] =["A"];
+
+kn={};
+kn["7"] = {};
+kn["8"] = {};
+kn["9"] = {};
+kn["4"] = {};
+kn["5"] = {};
+kn["6"] = {};
+kn["1"] = {};
+kn["2"] = {};
+kn["3"] = {};
+kn["0"] = {};
+kn["A"] = {};
+kn["7"]["7"] =["A"];
+kn["7"]["8"] =[">A"];
+kn["7"]["9"] =[">>A"];
+kn["7"]["4"] =["vA"];
+kn["7"]["5"] =["v>A",">vA"];
+kn["7"]["6"] =["v>>A",">v>A",">>vA"];
+kn["7"]["1"] =["vvA"];
+kn["7"]["2"] =["vv>A","v>vA",">vvA"];
+kn["7"]["3"] =["vv>>A","v>v>A","v>>vA",">vv>A",">v>vA",">>vvA"];
+kn["7"]["0"] =["vv>vA","v>vvA",">vvvA"];
+kn["7"]["A"] =["vv>v>A","vv>>vA","v>vv>A","v>v>vA","v>>vvA",">vvv>A",">vv>vA",">v>vvA",">>vvvA"];
+kn["8"]["7"] =["<A"];
+kn["8"]["8"] =["A"];
+kn["8"]["9"] =[">A"];
+kn["8"]["4"] =["v<A","<vA"];
+kn["8"]["5"] =["vA"];
+kn["8"]["6"] =["v>A",">vA"];
+kn["8"]["1"] =["vv<A","v<vA","<vvA"];
+kn["8"]["2"] =["vvA"];
+kn["8"]["3"] =["vv>A","v>vA",">vvA"];
+kn["8"]["0"] =["vvvA"];
+kn["8"]["A"] =["vvv>A","vv>vA","v>vvA",">vvvA"];
+kn["9"]["7"] =["<<A"];
+kn["9"]["8"] =["<A"];
+kn["9"]["9"] =["A"];
+kn["9"]["4"] =["v<<A","<v<A","<<vA"];
+kn["9"]["5"] =["v<A","<vA"];
+kn["9"]["6"] =["vA"];
+kn["9"]["1"] =["vv<<A","v<v<A","v<<vA","<vv<A","<v<vA","<<vvA"];
+kn["9"]["2"] =["vv<A","v<vA","<vvA"];
+kn["9"]["3"] =["vvA"];
+kn["9"]["0"] =["vvv<A","vv<vA","v<vvA","<vvvA"];
+kn["9"]["A"] =["vvvA"];
+kn["4"]["7"] =["^A"];
+kn["4"]["8"] =["^>A",">^A"];
+kn["4"]["9"] =["^>>A",">^>A",">>^A"];
+kn["4"]["4"] =["A"];
+kn["4"]["5"] =[">A"];
+kn["4"]["6"] =[">>A"];
+kn["4"]["1"] =["vA"];
+kn["4"]["2"] =["v>A",">vA"];
+kn["4"]["3"] =["v>>A",">v>A",">>vA"];
+kn["4"]["0"] =["v>vA",">vvA"];
+kn["4"]["A"] =["v>v>A","v>>vA",">vv>A",">v>vA",">>vvA"];
+kn["5"]["7"] =["^<A","<^A"];
+kn["5"]["8"] =["^A"];
+kn["5"]["9"] =["^>A",">^A"];
+kn["5"]["4"] =["<A"];
+kn["5"]["5"] =["A"];
+kn["5"]["6"] =[">A"];
+kn["5"]["1"] =["v<A","<vA"];
+kn["5"]["2"] =["vA"];
+kn["5"]["3"] =["v>A",">vA"];
+kn["5"]["0"] =["vvA"];
+kn["5"]["A"] =["vv>A","v>vA",">vvA"];
+kn["6"]["7"] =["^<<A","<^<A","<<^A"];
+kn["6"]["8"] =["^<A","<^A"];
+kn["6"]["9"] =["^A"];
+kn["6"]["4"] =["<<A"];
+kn["6"]["5"] =["<A"];
+kn["6"]["6"] =["A"];
+kn["6"]["1"] =["v<<A","<v<A","<<vA"];
+kn["6"]["2"] =["v<A","<vA"];
+kn["6"]["3"] =["vA"];
+kn["6"]["0"] =["vv<A","v<vA","<vvA"];
+kn["6"]["A"] =["vvA"];
+kn["1"]["7"] =["^^A"];
+kn["1"]["8"] =["^^>A","^>^A",">^^A"];
+kn["1"]["9"] =["^^>>A","^>^>A","^>>^A",">^^>A",">^>^A",">>^^A"];
+kn["1"]["4"] =["^A"];
+kn["1"]["5"] =["^>A",">^A"];
+kn["1"]["6"] =["^>>A",">^>A",">>^A"];
+kn["1"]["1"] =["A"];
+kn["1"]["2"] =[">A"];
+kn["1"]["3"] =[">>A"];
+kn["1"]["0"] =[">vA"];
+kn["1"]["A"] =[">v>A",">>vA"];
+kn["2"]["7"] =["^^<A","^<^A","<^^A"];
+kn["2"]["8"] =["^^A"];
+kn["2"]["9"] =["^^>A","^>^A",">^^A"];
+kn["2"]["4"] =["^<A","<^A"];
+kn["2"]["5"] =["^A"];
+kn["2"]["6"] =["^>A",">^A"];
+kn["2"]["1"] =["<A"];
+kn["2"]["2"] =["A"];
+kn["2"]["3"] =[">A"];
+kn["2"]["0"] =["vA"];
+kn["2"]["A"] =["v>A",">vA"];
+kn["3"]["7"] =["^^<<A","^<^<A","^<<^A","<^^<A","<^<^A","<<^^A"];
+kn["3"]["8"] =["^^<A","^<^A","<^^A"];
+kn["3"]["9"] =["^^A"];
+kn["3"]["4"] =["^<<A","<^<A","<<^A"];
+kn["3"]["5"] =["^<A","<^A"];
+kn["3"]["6"] =["^A"];
+kn["3"]["1"] =["<<A"];
+kn["3"]["2"] =["<A"];
+kn["3"]["3"] =["A"];
+kn["3"]["0"] =["v<A","<vA"];
+kn["3"]["A"] =["vA"];
+kn["0"]["7"] =["^^^<A","^^<^A","^<^^A"];
+kn["0"]["8"] =["^^^A"];
+kn["0"]["9"] =["^^^>A","^^>^A","^>^^A",">^^^A"];
+kn["0"]["4"] =["^^<A","^<^A"];
+kn["0"]["5"] =["^^A"];
+kn["0"]["6"] =["^^>A","^>^A",">^^A"];
+kn["0"]["1"] =["^<A"];
+kn["0"]["2"] =["^A"];
+kn["0"]["3"] =["^>A",">^A"];
+kn["0"]["0"] =["A"];
+kn["0"]["A"] =[">A"];
+kn["A"]["7"] =["^^^<<A","^^<^<A","^^<<^A","^<^^<A","^<^<^A","^<<^^A","<^^^<A","<^^<^A","<^<^^A"];
+kn["A"]["8"] =["^^^<A","^^<^A","^<^^A","<^^^A"];
+kn["A"]["9"] =["^^^A"];
+kn["A"]["4"] =["^^<<A","^<^<A","^<<^A","<^^<A","<^<^A"];
+kn["A"]["5"] =["^^<A","^<^A","<^^A"];
+kn["A"]["6"] =["^^A"];
+kn["A"]["1"] =["^<<A","<^<A"];
+kn["A"]["2"] =["^<A","<^A"];
+kn["A"]["3"] =["^A"];
+kn["A"]["0"] =["<A"];
+kn["A"]["A"] =["A"];
+
+
+codes = [l for l in open("21.in","r").read().strip().split("\n")];
+
+def GenDirMoves(mystring, seqs):
+ options = [seqs[x][y] for x, y in zip("A" + mystring, mystring)]
+ return ["".join(x) for x in itertools.product(*options)]
+
+@cache
+def CalcSteps(DirMoves,PrevPos,depth):
+ if depth == 1:
+ return sum(len(dn[pos1][pos2][0]) for pos1,pos2 in zip("A"+DirMoves,DirMoves));
+ score = 0;
+ for pos1, pos2 in zip("A"+DirMoves,DirMoves):
+ score += min(CalcSteps(substring,"A",depth-1) for substring in dn[pos1][pos2])
+ return score;
+
+def get_score(inputs,NumberOfRobots):
+ total = 0;
+ for data in inputs:
+ options = GenDirMoves(data,kn);
+ LengthOfShortestSequence = float('inf');
+ for op in options:
+ LengthOfShortestSequence = min(LengthOfShortestSequence,CalcSteps(op,"A",NumberOfRobots))
+ NumericPartOfCode = int(data[:-1]);
+ Complexity = NumericPartOfCode*LengthOfShortestSequence;
+ total += Complexity;
+ return total;
+
+part1 = get_score(codes,2);
+part2 = get_score(codes,25);
+
+print("part 1 =", part1);
+print("part 1 =", part2);
+
diff --git a/2024/aoc2024-d22.py b/2024/aoc2024-d22.py
new file mode 100644
index 0000000..5c4c809
--- /dev/null
+++ b/2024/aoc2024-d22.py
@@ -0,0 +1,79 @@
+#advent of code day 22
+#day 22
+#first puzzle I did after getting filtered by day 19
+#easy enough, just needed to calculate the prices per sequences
+#before calculating the sums to speed things up
+#for part 1 jsut calculate the new secret number after 2000 iterations
+#for part 2: make 2 dictionaries with secret number indexes as keys
+#each value in both dictionaries are lists
+#1 dictionary is for how prices are changing, the other one is current prices
+#make a set of all possible sequences from all secret keys based on price change dictionary
+#make a 3rd dictionary which has secret number as key and another dictionary as value
+#the nested dictionary contains prices based on a sequence
+#after that just iterate over the set of possible sequences make a sum
+#of the prices for that sequence, choose the highest
+#one thing that lead to a wrong answer was that I was constantly updating
+#the price in 3rd dictionary, but according to problem descriptions
+#monkeys sell the first time the sequence occurs, which means I needed to
+#only register the price once, then skip the sequence next time it occurs in
+#given secret number
+
+SecretNumbers = [int(l) for l in open("22.in","r").read().strip().split("\n")];
+
+def CalcSecret(previous_number):
+ next_number = (previous_number<<6)^previous_number;
+ next_number = next_number%16777216;
+ next_number = (next_number>>5)^next_number;
+ next_number = next_number%16777216;
+ next_number = (next_number<<11)^next_number;
+ next_number = next_number%16777216;
+ return next_number;
+
+print("please wait 1/3");
+part1 = 0;
+step = 2000;
+changes = {};
+prices = {};
+#calculate the 2000 iterations of each secret number
+#sum up all values of 2000th iteration for part 1
+#store the prices of each number for each iteration
+#store the change in price of each number for each iteration
+for num_i,num in enumerate(SecretNumbers):
+ num_old = 0 + num;
+ changes[num_i] = [];
+ prices[num_i] = [];
+ prices[num_i].append(num%10);
+ for s in range(step):
+ num_new = CalcSecret(num_old);
+ changes[num_i].append(num_new%10-num_old%10);
+ prices[num_i].append(num_new%10);
+ num_old = 0 + num_new;
+ part1 += num_old;
+
+print("please wait 2/3");
+#create a set of all possible sequences
+#store the price of a secret number when given sequence occurs
+prices_per_seq = {};
+sequences = set();
+for num_i in changes:
+ prices_per_seq[num_i] = {};
+ for j in range(3,len(changes[num_i])):
+ subseq = tuple(changes[num_i][j-3:j+1]);
+ sequences.add(subseq);
+ price_sub = prices[num_i][j+1];
+ if subseq in prices_per_seq[num_i]: continue;
+ prices_per_seq[num_i][subseq] = price_sub + 0;
+
+print("please wait 3/3");
+#for each sequence calculate the sum of prices for each secret number
+#when given sequence occurs. Store the highest sum for part 2
+part2 = 0;
+for s_i,sequence in enumerate(sequences):
+ print(s_i+1,"/",len(sequences)); #to make sure the code still runs
+ score = 0;
+ for num_i in prices_per_seq:
+ score += prices_per_seq[num_i].get(sequence,0);
+ part2 = max(score,part2);
+
+print("part 1 =", part1);
+print("part 2 =", part2);
diff --git a/2024/aoc2024-d23.py b/2024/aoc2024-d23.py
new file mode 100644
index 0000000..ca87fb3
--- /dev/null
+++ b/2024/aoc2024-d23.py
@@ -0,0 +1,73 @@
+#advent of code 2024
+#day 23
+#this is rewritten to adapt for part 2. All possible sets of computers
+#are searched for recursively (with FindNetwork function)
+#and stored in subnetworks set. Then it iterates over all elements of
+#subnetworks set. If the element has 3 computers, then it iterates over
+#all computers in the set. If at least one computer starts with "t",
+#it is counted towards part 1. We keep track of the longest set of
+#computers and update part2 variable when it changes.
+#The way FindNetwork works is that it takes a name of PC and a set of previously
+#visited computers that are all iterconnected. Then the given PC is added
+#to the set, the set is sorted (to avoid duplicates) and added to
+#subnetworks set. It iterates over all computers connected to PC from argument
+#according to Connections dictionary. If the computer is already in
+#the cluster, it is skipped. Then, we nest another loop to check if the
+#connections of the computer match the computers in the cluster.
+#if not, it's skipped. if yes, it call the FindNetwork function again
+#with the computer and updated cluster (NextCluster) as arguments.
+#infinite loop is prevented while checking whether or not the computer
+#is already in the cluster. Runtime is sped up due to discarding already
+#visited clusters accoroding to subnetworks set.
+#the password for part2 is just a string of computer names separated with commas
+#similar to previous day
+#I think the correct term is "ring" or "topology" but that's completely irrelevant
+#for the puzzle
+
+Connections = {};
+f = open("23.in","r");
+for l in f:
+ PC1, PC2 = l.strip().split("-");
+ if PC1 not in Connections: Connections[PC1] = set();
+ if PC2 not in Connections: Connections[PC2] = set();
+ Connections[PC1].add(PC2);
+ Connections[PC2].add(PC1);
+f.close();
+
+subnetworks = set();
+
+def FindNetwork(PC,Cluster):
+ NextCluster = Cluster.copy();
+ NextCluster.add(PC);
+ NextCluster = sorted(NextCluster);
+ if tuple(NextCluster) in subnetworks: return;
+ subnetworks.add(tuple(NextCluster));
+ for NextPC in Connections[PC]:
+ ok = True;
+ if NextPC in Cluster: continue;
+ for OtherPCs in NextCluster:
+ if OtherPCs not in Connections[NextPC]: ok = False;
+ if not ok: continue;
+ FindNetwork(NextPC, set(NextCluster));
+
+
+for i,computer in enumerate(Connections):
+ print(i, "/", len(Connections));
+ FindNetwork(computer,set());
+
+part1 = 0;
+part2 = None;
+LargestNetworkSize = 0;
+for subnet in subnetworks:
+ SubnetSize = len(subnet)
+ if SubnetSize == 3:
+ for computer in subnet:
+ if computer[0] == "t":
+ part1 += 1;
+ break;
+ LargestNetworkSize = max(LargestNetworkSize,SubnetSize);
+ if LargestNetworkSize == SubnetSize: part2 = subnet;
+
+part2 = ",".join(part2);
+print("part 1 =",part1);
+print("part 2 =",part2);
diff --git a/2024/aoc2024-d24.py b/2024/aoc2024-d24.py
new file mode 100644
index 0000000..49d2c6f
--- /dev/null
+++ b/2024/aoc2024-d24.py
@@ -0,0 +1,82 @@
+#advent of code 2024
+#day 24
+#part 2 requires reading the input, then either solve by hand (like I did)
+#or make some kind of detection of abnormailities (for example,
+#z-wires should always by outputted by XOR gate,
+#output of x XOR y should always go as input to another XOR connected to z-wire etc
+#basically the whole program is just a lot of adder gates,
+#adding all bits from x and y wires and outputting them into z-wires,
+#mixed together with 8 errors.
+#part 1 is kind of shitty so I won't bother explaining.
+#I should probably implement some kind of verification of calculations
+#that is: calculate the z in binary, calculate the x and y in binary
+#and calculate the z as a sum of x and y
+
+import re
+data_initial, data_gates = open("24.in","r").read().split("\n\n");
+
+Wires = {};
+AllGates = {};
+
+def calc(IN01,GATE,IN02,OUT):
+ CurrentValue = Wires.get(OUT,False);
+ if GATE == "AND": Wires[OUT] = Wires[IN01] & Wires[IN02];
+ elif GATE == "OR": Wires[OUT] = Wires[IN01] | Wires[IN02];
+ elif GATE == "XOR": Wires[OUT] = Wires[IN01] ^ Wires[IN02];
+
+for line in data_gates.strip().split("\n"):
+ in1, gt, in2, out = re.findall(r'\w+',line.strip());
+ AllGates[(in1, gt, in2, out)] = False;
+
+for line in data_initial.split("\n"):
+ wire, wireval = line.strip().split(": ");
+ Wires[wire] = bool(int(wireval));
+
+go = True; #keep hgoing until all gates were used
+while go:
+ go = False;
+ for Gate in AllGates:
+ if AllGates[Gate]: continue; #skip gates which already ran
+ go = True;
+ a1,a2,a3,a4 = Gate;
+ if Wires.get(a1,"notyet") != "notyet" and Wires.get(a3,"notyet") != "notyet":
+ calc(*Gate);
+ AllGates[Gate] = True;
+
+X_set = [];
+Y_set = [];
+Z_set = [];
+for w in Wires:
+ if w[0] == "x": X_set.append((w,int(Wires[w])));
+ if w[0] == "y": Y_set.append((w,int(Wires[w])));
+ if w[0] == "z": Z_set.append((w,int(Wires[w])));
+
+Xvals = reversed(sorted(X_set, key = lambda m: m[0]));
+Yvals = reversed(sorted(Y_set, key = lambda m: m[0]));
+Z_set = reversed(sorted(Z_set, key = lambda m: m[0]));
+Xbinary, Ybinary, Zbinary = "","","";
+for zzz in Z_set: Zbinary += str(zzz[1]);
+for yyy in Yvals: Ybinary += str(yyy[1]);
+for xxx in Xvals: Xbinary += str(xxx[1]);
+Zcorrect = int(Xbinary,2) + int(Ybinary,2);
+Zcorrbin = str(bin(Zcorrect));
+print("diagnostics");
+print("binary X = ", Xbinary);
+print("binary Y = ", Ybinary);
+print(f'(addition) {"-"*len(Zcorrbin)}');
+print("binary Z =", Zcorrbin);
+print();
+print("decimal X =", int(Xbinary,2));
+print("decimal Y =", int(Ybinary,2));
+print("decimal Z =", Zcorrect, "<- this should be the output for part 2");
+print("false Z =", Zbinary);
+
+part1 = int(Zbinary,2)
+print();
+print("part 1 = ", part1);
+#hardcoded for my input, I did it with pen and paper
+FoundIncorrectWires = ["wbw","wgb","jct","z39","gwh","z09","rcb","z21"];
+FoundIncorrectWires = sorted(FoundIncorrectWires);
+part2 = ",".join(FoundIncorrectWires);
+print("part 2 = ", part2);
+
diff --git a/2024/aoc2024-d25.py b/2024/aoc2024-d25.py
new file mode 100644
index 0000000..4f14ee6
--- /dev/null
+++ b/2024/aoc2024-d25.py
@@ -0,0 +1,55 @@
+#advent of code 2024
+#day 25
+#I'm not sure if my solution is general enough to work on modified inputs
+#like bigboys but I guess it's good enough to work on original AOC one.
+#parse schematics, decide if they are a key or a lock based on first row
+#generate the heights of each pin
+#compare each key with each lock column by column
+
+data = open("25.in","r").read().strip().split("\n\n");
+
+def getDimensions(gridraw):
+ Category = None;
+ Schematics = {};
+ xMax = 0;
+ yMax = 0;
+ for y, line in enumerate(gridraw.strip().split("\n")):
+ yMax = max(yMax,y);
+ if y == 0:
+ if line == "."*len(line): Category = "key";
+ else: Category = "loc";
+ for x, c in enumerate(line):
+ xMax = max(xMax,x);
+ Schematics[(y,x)] = c;
+ heights = [];
+ if Category == "key":
+ for x in range(xMax+1):
+ h = sum( Schematics[(y,x)]=="#" for y in range(1,yMax) );
+ heights.append(h);
+ else:
+ for x in range(xMax+1):
+ h = sum( Schematics[(y,x)]=="#" for y in range(yMax-1,0,-1) );
+ heights.append(h);
+ return Category, heights;
+
+Keyed = [];
+Locked = [];
+XMAX = 0;
+YMAX = 0;
+XMAX = len(data[0].split("\n")[0])
+YMAX = len(data[0].split("\n")) -2;
+for d in data:
+ cat, dims = getDimensions(d);
+ if cat == "key": Keyed.append(dims);
+ if cat == "loc": Locked.append(dims);
+
+part1 = 0;
+for Key in Keyed:
+ for Lock in Locked:
+ fit = True;
+ for col in range(XMAX):
+ if Key[col] + Lock[col] > YMAX: fit = False;
+ part1 += 1*fit;
+
+print("part 1 =", part1);
+