Skip to content

monpie3/adventOfCode2024

Repository files navigation

adventOfCode2024 🎄

Python application codecov

You can find the puzzle inputs here: https://adventofcode.com/2024

Recap of my progress in Advent of Code so far:

  • 2024: 25 days (43 ⭐)
  • 2023: 22 days (36 ⭐)
  • 2020: 10 days (19 ⭐)

What have I learned this year?

Day 1

  • How to use Git credential store on WSL → 🔗
  • How to change the default editor from nano to vim → 🔗

Day 2

meme from day 2

Day 3

  • A good reminder that . by default matches any character except a new line. So, I learned about DOTALL flag.
  • I came across this website: https://regexcrossword.com/

meme from day 3

Day 4

meme from day 4

Day 5

  • The better solution is to use sorting with a custom comparator function →

better solution for day 5

Day 6

  • Used time python Day_06/task_6b.py to measure the execution time of the script

meme from day 6

secend meme from day 6

Day 7

  • The go-to solution: itertools.product

meme from day 7

Day 8

  • Using the next iterator from itertoolscombinations

  • A cleaner way to handle this kind of logic. Instead of writing:

if cell not in antenna_positions:
    antenna_positions[cell] = [(row_ind, col_ind)]
else:
    antenna_positions[cell].append((row_ind, col_ind))

We can refactor it to be more concise: antenna_positions.setdefault(cell, []).append((row_ind, col_ind))

Alternatively, we can use defaultdict from collections, which automatically provides a default value for the key that doesn't exist.

Day 09

In part two, I got rid of OrderedDict since I hadn't actually used it in part one anyway. To improve performance, I changed the structure from index: file_id to file_id: [list of indices], as searching through the old dictionary was painfully slow. I also replaced the while loop with a for loop (always a pleasure) 😇

Day 10

Been there, done that 😅

meme from day 10

Day 11

The choice of a dynamic list as a data container was not the best. It turns out that order was not important at all, and it would have been much better to use a hash map where the key would be the engraved number and the value the number of occurrences.

nicer solution for day 11

I also read that returning from a with block (e.g., when opening a file) works fine, and the file is automatically closed. The with statement ensures cleanup happens, whether the block exits normally or via a return. 🔗

Day 12

I probably would have done better if my puzzle_input had been correct instead of last year's 😅 But this was one of those tasks I really liked (aside from the wrong input), where I had to grab a sheet of paper and a pen to plan it out. What I’m proud of is that I came up with the correct perimeter formula right away. Even though it’s a bit basic, it works!

amazing trolling from JS

Day 13

I didn't want to reinvent the wheel, so I just went with sympy. Two equations with two unknowns. It was fun to solve the test examples on paper.

meme partially about day 13

Day 14

In my case, searching for the Easter egg using the smallest safety factor didn’t work (though it turns out it was the second smallest result!). However, I listed all the possible cases, and after entering 5000 as an answer, I knew the range I should focus on (which at least halved my search efforts 😅).

I think it wasn’t the best idea to store robot positions as JSON. Using images with PIL would probably have been faster, but I wanted to avoid libraries that require additional installation (tqdm doesn’t count—it’s just a little helper to check if my program is still running).

I know some people enjoyed this task, but after browsing Reddit, I feel it’s too input-dependent. I don’t like that someone might solve it just because they got lucky, rather than because their method works for everyone or because they noticed something critical in the task description. It feels like the solutions on Reddit are post hoc reasoning because, based on the task description, we didn’t even know what we were looking for.

my tree

What helped me optimize this task is that the robots align horizontally and vertically after every map width or map height iteration. In the vertical alignment we see all x repetitions with pseudorandom y and in the horizontal alignment we see all y repetitions with pseudorandom x. 🔗

So, after seeing the generated data, I was able to determine that my vertical offset (X) is 9 and horizontal offset (Y) is 65, which allowed me to narrow down the range being searched.

Additionally, once we find X and Y, we can simply find the smallest number N, where:

N%101==X and N%103==Y 🔗

101 is grid's width, 103 - length.

There's the interesting Chinese Remainder Theorem, which is implemented in sympy and allows solving this problem in a few lines from this point 🔗 :

from sympy.ntheory.modular import crt

moduli = [101, 103]
remainders = [9, 65]
N,_ = crt(moduli, remainders)

where N is the solution, a place where both alignments, vertical and horizontal, coincide.

visualisation - day 14

meme from day 14

Day 17

The match statement is a more elegant alternative to if...elif...else

Plus, instead of struggling with split to parse a file, like this:

def load_data(filename):
    with open(filename, "r") as file:
        registers, program = file.read().split("\n\n")

    registers = [int(register.split(": ")[1]) for register in registers.split("\n")]
    program = program.strip().split(": ")[1].split(",")
    return registers, list(map(int, program))

I could simply use regex and unpack it in one line: a, b, c, *prog = map(int, re.findall(r'\d+', open('in.txt').read())) 🔗

Day 18

Brute force was good enough, so I stuck with it. Apparently, you can practically reuse part 2 from day 16 (you just need to remove the condition about 1000 points → 🔗), but I don’t have that part ready yet, so we’ll see in the future 😅

meme from day 18

Day 19

Part I: Initially, I tried to avoid checking all towel patterns and thought I could prevent the algorithm from being too greedy by starting with the largest patterns.

max_len = max([len(pattern) for pattern in towels])
to_check = design
while len(to_check) > 0:
    is_found = False
    for i in range(max_len):
        if to_check[:i] in towels:
            to_check = to_check[i:]
            is_found = True
            break
    if not is_found:
        return False
return True

This approach worked in cases like this: Towels: ab, abc, def Design: abcdef

Even though the first towel pattern was ab, because I started from the sorted list, I checked abc first. Combining abc and def gave the correct result: abc + def = abcdef.

However, while the algorithm was still greedy, it was greedy from the end 😅.

For example: Towels: abcd, abc, def Design: abcdef

Starting from the longest pattern (abcd), the remainder ef didn’t match. The correct configuration, abc + def = abcdef, was missed because the algorithm didn’t check all possible combinations.

So, I abandoned the while loop and switched to recursion. There, I check all the patterns, and if there is a combination of patterns that matches, the function returns True. If the design fails at some point, it backtracks to the previous state and tries the next possible pattern.

Additinaly, I could simplify function by replacing the loop:

valid = 0
for design in designs:
    valid += is_design_valid(design)

with sum(map(is_design_valid, designs))

meme from day 19

Day 20

Part 1: I forgot that two picoseconds are needed for the cheating, so the distance isn’t simply the difference between the end position index and the start position index—we need to reduce it by 2.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages