Advent of Code 2023 Notes
Advent of Code (AoC) is an yearly event where daily, from Dec 1 to Dec 25 a programming problem is released. It starts very easy and the difficulty progresses each day. Each day has two parts, both have the same input but the second part is harder.
This is the 3rd year I do it, but the first time I take notes documenting what I've done; the code for everything can be found here (GitHub repo). The code isn't clean and it isn't meant to be, it's just a quick solution to the problem. I might revisit some of them later to make them more readable/efficient.
I used Python because I solved them (until the later days) as they appeared, but for the next year I'd like to try on a new language every day. Until then I might do the 2020 (or earlier) AoC with other programming languages.
Day 1
Place 3612/2104, code
Easy (well it's the first day), but I saw many complain that it was significantly harder than other year's first day.
- For the first part I just iterated through every character and saved the first digit and updated the last.
- For the second part I've heard most struggle because of "oneight" and edge cases like that, but I used
str.find()
andstr.rfind()
instead of replacing digits so I didn't even had to think about it.
Day 2
Place 1269/1301, code
It was just a matter of splitting the input, super simple but I definitely could've used regex instead for a cleaner code.
Day 3
Place 2004/1596, code
I'd benefit a lot if I'd use more numpy. For now I've only used it for padding the matrix and np.zeros_like()
. I replaced the dots from the input with None
to make my life easier.
- For the first part I iterated the matrix, once I saw a number I tried constructing it and keeping in a variable if I saw any neighboring symbol.
- For the second part I made another matrix where I store for each "gear" a tuple (count, product) and at the end summed all gears that have count=2, not very efficient memory wise (in hindsight a map would've been better) but it works.
Day 4
Place 2938/2896, code
First part was easy and for the second part thought I'd have to do more. I just added to cards[j*card+1]
cards[card]
(where j <= score
) if the score of the current card wasn't 0.
Day 5
Place 4450/27172, code
For part 1 just did what was asked and for the second part I had somewhere to go and didn't get to finish until later in the evening. I know it's just a matter of calculating the intervals but I didn't feel like it so I did the same but in C++ and let it run for a few hours. I'd really like to come back to this when I have time.
Day 6
Place 3672/2459, code
For both parts I didn't make it efficient, just did what the description told me. It took a few seconds for the second part to run so maybe I'll come back sometime to make it more efficient.
Day 7
Place 3599/3098, code
The first part was straight-forward but it took a while to understand everything I need to keep track of.
The second part didn't take much because I only needed to add an if to an already existing function to handle the Joker card.
Day 8
Place 3441/3597, code
For the first part I just did what it said, and for the second it was obvious it was something periodical and used math.lcd()
and once I got to the first --Z
value for every --A
, I pop'd it because it wasn't needed anymore. I thought it won't work because the description doesn't enforce a single ending but it was okay.
Day 9
Place 1814/4985, code
It was insanely easy but for the second part it took 20 minutes to realise I needed to reverse a list because the results were almost good.. but it's fine :) (I really want to sleep).
Day 10
Place 3710/4805, code
It seems like it was one of the hardest.
The first part was a simple Lee algorithm.
For the second part I added after every row/column another row/column that connects the main pipe with "|" and "-". Then it was just a simple flood fill on the outside and remove the added columns/rows then count how many weren't filled by the flood fill.
Day 11
Place 26604/24390, code
Very easy but I had to leave in the morning and was out the entire day;
instead of creating the matrix it was just a matter of calculating how many empty rows/columns are before a point. For the second part just a number was changed to make sure you can't go the way of making the matrix and duplicating rows/columns.
Day 12
Place 1777/10660, code
Definitely the hardest one yet. For the first part I just tried and replaced each '?' symbol.. obviously this did not work for the second part, and I also had to leave. At first I thought it would be something more mathematical, it wasn't instantly obvious it would be dynamic programming with caching even though each year has at least one problem like this. I also simplified each row before.
Day 13
Place 1172/8171, code
Pretty straight-forward (fastest part 1 for me) but I had to go to a class. Numpy was super helpful on this one (and I find myself using it more for each problem). The second part has just some lines changed from instantly breaking once a line is different to saving the number of differences between lines (np.sum(arr1 != arr2)
).
Day 14
Place 24628/18506, code
Unfortunately I was busy today too. The first part was easy, the second part was obvious that the states were repeating. Nonetheless, I had a lot of small bugs that took a while to be found. I really think I should've used np.array
for performance but I'm more comfortable with lists of lists.
Day 15
Place 7921/7107, code
Extremely easy for a day 15, all you had to do was follow the instructions. It literally took 10 minutes in total and runs fast.
Day 16
Place 6212/5735, code
It was fairly easy, to make it faster I saved for every cell if I already got to it and from what direction so I did not repeat a path. For part 2 I just checked every starting point, to my surprise it was fast enough. Probably could've made something smarter.
Day 17
Place 21600/20570, code
Woke up, read the problem, got back to sleep. I also have decided that for next year I'm writing my own custom templated Dijkstra algorithm.
Unfortunately from this day forward I'll have no time to do them the day they appear.
I ended up solving it during that day but I had a what looked like off-by-one error. I had a function that returns the possible next steps for Dijkstra and apparrently I also needed to take into account that the next step can't be backward (before I only removed the same direction steps).
For the second part I just needed to add 2 parameters for min/max step in my next_steps function.
Day 18
Place 26026/21259, code
Part 1 was extremely easy but it was obvious that the making of the matrix is not the right way. I ended up learning about the Pick's theorem that provides a formula for the area of a polygon knowing boundary points and the points (integers) within the shape. It feels really magical. From it I needed the interior points, using the area from the shoelace formula. The solution feels a little too math-y but it's fast.
Day 19
Place 25733/19015, code
For part 1 I just did what the problem said. For the second part I played with the intervals and used a queue to go through every single one.
Day 20
Place 19078/15003, code
Part 1 was fun, first time using classes this AoC year.. but I really didn't like part 2. It works on the assumption that the input is carefully crafted and the solution is using LCM on the Conjunctions of the Conjunction linked to rx
. Will probably come back to the code to make it more OOP.
Day 21
Place 20852/11097, code
For part 1 I did a straight-forward Lee's algorithm and counted the cells that had different parity to max_step
. For part 2 np.tile
was useful, but obviously wasn't enough. I knew there had to be a formula and got the hint that it is the quadratic formula. I had lots of bugs, like for starters (pun intended) I forgot to get rid of the start point when tiling the matrix...
Day 22
Place 14181/12938, code
I actually loved the problem and it would've been way faster if the example input was more complex for part 2. Probably could rewrite with a little bit of OOP. I used a map that contains every cube (coord: brick_id
) and a map that contains every brick what cubes it has.
For the falling part I checked for every cube in a brick if it has nothing below (done until nothing is updated).
I construct 2 maps that contain (a) if x is directly above y and (b) if x is directly below y. These made it easy to find the disintegrable bricks. If I was to redo this, I would definitely contruct a graph.
For part 2 it took a lot because I kept finding almost good solutions and was confused why it wasn't correct. If I had used graphs there probably was an algorithm for this to be more efficient, but I brute-forced it so it takes a few seconds in Python. I have a queue in which I put every brick above the current one and remove from the set
where I store the ones below (simulating the "falling"). At the end I check if there are no bricks below every brick.
Day 23
Place 15306/13265, code
It was the longest day but it teached me the importance of a good debugging environment.. First I tried to do a Lee/Fill on the maze, clearly it wasn't good, then I tried to make every intersection into a node and created a graph to use Dijkstra.. bad idea because of cyclical parts.
Then I got the idea to use the slopes for my advantage and instead of using the intersections, I used the slopes as nodes. This took a while because it was almost good enough and at times it gave the good answer for the example.
I realised I had some bugs with the nodes-creation logic and solved it. For part 2 it was easy as I used the intersections-are-nodes graph generator, the first attempt from part 1. Remembered I could speed up the process by using networkx
library and... it took too much, so I simplified the graph by removing nodes that have only two neighbouring nodes. It takes 2 minutes and will never again see the light of day.. until next year when a similar problem will show itself probably.
Day 24
Place 14934/10354, code
Part 1 was basic algebra formulas and for part two.. I used the z3
library solver, inspired by other peoples solutions, but it was slow. Tried to simplify, got rid of the t
variable and that was it. A little too much math in this one. I should learn to use z3
more because it is an interesting library.
Day 25
Place 11794/9052, code
For the last problem I first tried to brute force and remove any 3 edges, but it was too slow. Then I searched algorithms from networkx
because it seemed like something that could already exist implemented, and found nx.minimum_cut
that did the closest I could find. Checked every 2 nodes and if the minimum cut was 3, that was my solution.