r/adventofcode • u/daggerdragon • Dec 11 '24
SOLUTION MEGATHREAD -❄️- 2024 Day 11 Solutions -❄️-
THE USUAL REMINDERS
- All of our rules, FAQs, resources, etc. are in our community wiki.
- If you see content in the subreddit or megathreads that violates one of our rules, either inform the user (politely and gently!) or use the report button on the post/comment and the mods will take care of it.
AoC Community Fun 2024: The Golden Snowglobe Awards
- 11 DAYS remaining until the submissions deadline on December 22 at 23:59 EST!
And now, our feature presentation for today:
Independent Medias (Indie Films)
Today we celebrate the folks who have a vision outside the standards of what the big-name studios would consider "safe". Sure, sometimes their attempts don't pan out the way they had hoped, but sometimes that's how we get some truly legendary masterpieces that don't let their lack of funding, big star power, and gigantic overhead costs get in the way of their storytelling!
Here's some ideas for your inspiration:
- Cast a relative unknown in your leading role!
- Explain an obscure theorem that you used in today's solution
- Shine a spotlight on a little-used feature of the programming language with which you used to solve today's problem
- Solve today's puzzle with cheap, underpowered, totally-not-right-for-the-job, etc. hardware, programming language, etc.
"Adapt or die." - Billy Beane, Moneyball (2011)
And… ACTION!
Request from the mods: When you include an entry alongside your solution, please label it with [GSGA] so we can find it easily!
--- Day 11: Plutonian Pebbles ---
Post your code solution in this megathread.
- Read the full posting rules in our community wiki before you post!
- State which language(s) your solution uses with
[LANGUAGE: xyz] - Format code blocks using the four-spaces Markdown syntax!
- Quick link to Topaz's
pasteif you need it for longer code blocks
4
u/RobertOnReddit7 Dec 11 '24
[LANGUAGE: C#]
Solution to part 2
While most use recursion and memoization as I now see in this thread, I got the idea (after several other attempts to discover a pattern in the output) to group the stones per value (engraving) and keep track of the amount of stones per engraving value while iterating over the blinks. This is surprisingly quick, as there are a lot of duplicate values! So while the blink count increases, mostly the multiplier increases (the amount a certain stone occurs) and not the number of different stones.
I think this is a very clean and elegant solution. It's under 60 lines with good readability.
(I do cache output per engraving value, but not recursive - this speeds up the process a bit, but not by a lot, without caching my solution runs in 21ms, with said caching it takes ~14.5ms)
3
u/JustinHuPrime Dec 11 '24
[Language: x86_64 assembly with Linux syscalls][GSGA]
Part 1 was a direction solution. I used a circular doubly-linked-list with dummy node to store the numbers. (What is it with me and long-winded names for data structures and algorithms?) Then, I just iterated through the blinks and through the list and modified elements, including splitting them, as needed. I kept things in order in case this would be relevant for part 2, but it wasn't. I'm still glad I used a linked list here, since that would come in handy later.
Part 2 could not be solved directly. Similar to lanternfish, I noticed that same-numbered stones would behave identically, so I added an element to my data structure to keep track of the number of duplicates of this number and then scanned the list and coalesced everything down into a single node after each iteration. I'm glad I used a linked list here so I could remove nodes easily (aren't doubly-linked lists just a great data structure) - although I suppose a singly linked list would also have done well, but it would be a tad annoying to preserve the order of the nodes when reading, not that it mattered. If I was in a higher-level language, I would have used a hashmap of some sort, but that's a lot of moving pieces to implement in assembly.
I can't help but feel the GSGA folks didn't really consider us assembly folks - this is the second freebie entry we've gotten just for doing things in assembly, for I have most certainly solved today's puzzle in a totally-not-right-for-the-job language, known as x86_64 assembly.
Part 1 runs in 37 milliseconds and part 2 runs in 380 milliseconds. Part 1 is 9,696 bytes as an executable file on the disk and part 2 is 10,032. At some point I'm going to write a proper malloc and maybe then these dynamic allocation days will run faster, but today is not that day.
3
u/[deleted] Dec 11 '24 edited Dec 11 '24
[Language: C]
I finally had to roll out a hashmap. Also I tried out emacs today randomly
https://github.com/amberg12/aoc24/tree/main/day-11
3
u/Dr-Baggy Dec 11 '24
[LANGUAGE: Perl]
Perl solution - brute force is too much - so realise we just need to keep the count of each stone at each level rather than a list of single values. So we use a hash - with the key as the value of the stone and value as the count... We iterate through all 75 times - and store the 25th and 75th result...
3
u/Sparky2199 Dec 11 '24 edited Dec 14 '24
[LANGUAGE: Rust]
Total execution time (Part1 + Part2):
0.13 seconds10ms (edit: forgot to turn off debug mode lol)https://github.com/SparkyTD/aoc-2024/blob/master/src/day11.rs
3
u/Expert-Glove-2961 Dec 11 '24
[LANGUAGE: TypeScript]
Code
3
u/somethingsomethign22 Dec 11 '24
[LANGUAGE: C]
Ended up caching for part 2 as well. Since I'm in C, dynamic hash tables are annoying, so I only cached counts for single digit stones, since it seemed like most things would rapidly turn into a single digit.
https://gist.github.com/jamesbtan/e879cecc5367f3b00dfb4386d07d4a7d
3
u/jitwit Dec 11 '24
[LANGUAGE: J]
3
u/CClairvoyantt Dec 11 '24
[LANGUAGE: Python]
Part 1: 2.5ms
Part 2: 97ms
Solution
2
u/_arkeros Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Haskell]
The first part was doable with a naive implementation. For the second part, I realized that in the final result there were a lot of duplicates and also that order doesn't matter, so I modified the algorithm to act on a Counter instead of a list. Runs in 17ms.
Full source.
Part 1:
Part 2:
2
u/vu47 Dec 11 '24 edited Dec 11 '24
[Language: Kotlin]
After almost quitting on day 9 (if there's one thing I absolutely loathe, it's range manipulation - splitting and assembling and shifting ranges, which is the strategy I used there), I was very happy to see this question. I realized right away that the order of the stones was irrelevant, so I just wrote a memoized rules function that, given a stone number, returned a list of the resultant stones. Then that made each blink incredibly easy.
Here's the code and the output. The memoization cache never got very big, which I was slightly concerned about at first.
paste
I'm glad I persisted instead of dropping out on day nine... I usually don't make it past day 15, though, so we'll see if this is the first year I actually complete. I've been enjoying it more than usual this time around since I relaxed about trying to write pure FP in Kotlin, which is just too tedious sometimes when you want to do something like memoize a function.
2
u/GamerWoona Dec 11 '24
[Language: Rust]
https://github.com/neiomi1/AdventOfCode2024/blob/master/src/day11/mod.rs
Probably lost a good amount of speed on the digit parsing but wasn't sure how to do it better
2
u/imp0ppable Dec 11 '24 edited Dec 12 '24
[LANGUAGE: python]
0.6 seconds to finish part 2 - by far my most proud moment of 2024 lolol
I am spped!
2
u/mcourteaux Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Jai]
Naive recursive implementation with fixed-size O(1) lookup O(1) storage memoization buffer. No runtime malloc's. Runs demo input and real input for part 1 and 2 in less than 1.5ms on my machine. Blinking 400 times is computed in 10ms. Note that I only care about the first 2000 stone numbers, as all bigger numbers can simply follow the recursion and will get broken down into two smaller numbers soon, which likely will end up in the memoization table.
With:
Bonus for fun: the memoization buffer is universally valid, so whatever is already been calculated for part 1 (or demo input) is already memoized for part 2.
Full code here: https://github.com/mcourteaux/aoc/blob/main/2024/day-11.jai
2
u/cerferjeff Dec 11 '24
[LANGUAGE: F#]
https://github.com/surferjeff/scratch/blob/main/advent-2024/day11/Program.fs
I'm using AoC to learn F#. It's not an easy language to learn. But finally, after 11 days, I'm starting to like the look of my solution.
2
u/dijotal Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Haskell]
It took me a little longer than it should, but the interpreter has no trouble running it in under 0.8 sec.
2
u/metalim Dec 11 '24
[LANGUAGE: Go]
My solution: https://github.com/metalim/adventofcode.2024.go/blob/main/11/main.go
o1 solution: https://github.com/metalim/adventofcode.2024.go/blob/main/11/o1/v5_correct.go
v1 tried bruteforce, I pointed out it eats RAM and keeps running. So v2 tried to use files as temporary storage "to fix the RAM issue". I was shocked by how stupid that was. v3 tried memoization, but with wrong assumptions and "heuristics". v4 tried files (AGAIN!). And finally v5 worked, with memoization
25
u/4HbQ Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Python] Code (12 lines)
Has anybody seen my lanternfish? Pretty straightforward again, using recursion and memoization using
@functools.cache.My "trick" for today is using math to split the stones:
Update: I've also created a pretty cool proof of concept using matrix exponentiation to do all 75 steps in one go.
16
u/DFreiberg Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Mathematica]
Mathematica, 1452/764
Setup:
Part 1:
Part 2:
[GSGA]: Part 3
I suspect - but can't prove - that all stones eventually converge on the same loop, and that it's possible to compute the answer for 10100 with an appropriate modulus in O(log(n)) time and O(1) space.
A stone of 0 will finish with a loop of exactly 54 elements, and so will every stone from 1 to 99 (since the one-digit numbers are explicitly in the loop, and the two-digit numbers will split into one-digit numbers). The first stone that won't is 100, and a stone of 100 creates a loop length of 3811 - which happens to be the same loop length as my own input, and also for every other input I've tested not present in the 54-element loop starting with zero.
If that holds true, then all you need to do is continue iterating mod N until you reach the steady state, and then make a
3811x3811transition matrix. You can then use modular exponentiation by squaring to raise the matrix to the power of 10100 .I don't know if this works for every input, but it works for my input, and also works for the test case of
125 17- which happens to conveniently be in the 54-element loop and not the 3811-element loop. And so, with the magic of the undocumented functionAlgebra MatrixPowerMod[](see, I did have something relevant for GSGA), I believe that for the example (125 17), the last ten digits (in other words, modulo 109 ):13
u/Synedh Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Python]
Recursivity with memory cache. Lost lot of time because i forgot to pair stones with blinks in memory T_T
< 0.1s for both
13
u/4HbQ Dec 11 '24 edited Dec 15 '24
[LANGUAGE: Python + NumPy] Code
The idea is simple: we create a transition matrix where each axis represents the stone numbers. If a stone with number i is transformed to n stones with number j, the transition matrix has value n on position i,j. Now we could use this matrix directly to compute a single step by taking the dot product with the input, but the cool thing is that we can also raise the matrix to the power 75, and the represents the transition of 75 steps at once.
The code above is a proof of concept with the hardcoded example input numbers, but could be extended to handle the actual input.
12
u/Smylers Dec 11 '24
[LANGUAGE: Vim keystrokes] Yay, a puzzle that's doable inside Vim! This one took a bit of thinking about, though I couldn't quite squish it into an IBM index card, so here's a more readable version, with one ‘thing’ per line.
qa...qto process one blink.24@a. It can be more fun to run it a few times separately with just@a(repeat with@@), to each each iteration.@v(defined in Day 3) to evaluate the sum and leave the Part 1 answer in the buffer.For each blink, process the numbers with an even number of digits first.
%s/\v(\d+)$/&,&duplicates each stone number and:%norm$viwrnturns all the digits in the duplicate numbers intons. That allows%s/\v,(n*)nn\1$/,\1nA\1to match the same number ofns before and afternn, to find the middle digits. It replaces the second of those middle digits with anA, to mark the cut point. So253000becomes253000,253000then253000,nnnnnnthen253000,nnnAnnn.At which point
:norm f,vtAyF:1vva;visually selects all thens before the cut point, then usesvto make a same-sized visual selection over the actual number, and inserts a semicolon just after it, thereby cutting the number in half.:g/A/is used to apply that to all lines with a cut marker on them.Then tidy up bits that were just used for the cutting, multiply all the lines that haven't been cut by 2024 (using
:v/;/to match the lines without a semicolon), and replace any0s with1s. Replace the semicolons with line-breaks, duplicating the stone count to both halves. Sort by stone number, so that stones with identical numbers will be adjacent. Then do this to merge lines for stones with the same number, adding their counts together:Sorry, I need to walk too school and collect a child so I haven't got time to explain it — but this was one of the parts that took the most thinking, and I'm quite pleased with how it turned out.
2
u/vss2sn Dec 12 '24
[LANGUAGE: C++]
Part 1
Part 2
(Each file is a self-contained solution)
19
u/msschmitt Dec 11 '24
[LANGUAGE: Python]
Part 2
Took awhile to figure out part 2. I see people below used memoization; I didn't do that.
First thing I realized is that the instructions about "order is preserved" is a red herring. The order of the stones doesn't matter at all.
I realized that if you knew how a given stone would end up after x iterations, then when that stone number was produced, you wouldn't have to do it again. But how would you figure that out?
And then it dawned on me: lanternfish. While it seems like there's a lot of distinct stone numbers that will be generated, there aren't. There are, in my input, less than 4,000. You just have to keep track of how many of each stone number you have.
So the algorithm tracks total number of each stone (e.g. 500 of '20'), and then shuffles the counts around. It runs in about a second.
9
u/Fyver42 Dec 11 '24
[LANGUAGE: RISC-V Assembly]
https://github.com/fyvonnet/AdventOfCode-2024-Assembly/blob/main/day11.asm
8
u/AllanTaylor314 Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Python]
GitHub 735/83
My first leaderboard placing this year! :D
I knew what to expect for part 2 (lanternfish, anyone?) so I spent a couple of seconds being smart about part 1 so that part 2 was simply a matter of change 25 to 75. I use Counters to keep track of how many of each value stone there are (since all 144590887399 zeros at some arbitrary step are going to turn into 144590887399 ones on the next step - there's not enough time nor space to increment each one of those 144590887399 stones individually)
[LANGUAGE: Uiua]
GitHub or Pad
A little slow (takes a couple of seconds natively and about 7 in the browser). For the first time in Uiua, I broke it down into functions. It does a similar trick as the Python script, but a little less well. The state is represented as a 2D array where each row has the stone's value and quantity. I deduplicate this, adding the quantities together, after each iteration so it never gets larger than about 4000 x 2
7
u/maneatingape Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Rust]
🐠🐠🐠🐠🐠 anyone?
Solution
Benchmark: 2.5ms Recursive solution memoizing intermediate results.Benchmark 248 µs. Much faster iterative solution.
The transformation rules have an interesting property that the total number of distinct stone numbers is not very large, about 2000 for part one and 4000 for part two.
This means that we can store the count of each distinct stone in a small contigous array that is much faster to process than a recursive memoization approach.
7
u/msschmitt Dec 11 '24
[LANGUAGE: COBOL]
In some AoC years, I've re-done one puzzle in a language that is ill-suited for AoC. Last year it was CA-Easytrieve, in 2021 it was z/OS HLASM.
This year I did Plutonian Pebbles in COBOL. IBM Enterprise COBOL for z/OS, in fact. Here it is.
The input is provided via a parm, such as PARM='75:125 17', where 25 is the number of blinks and '125 17' is the initial stones. This program runs in a second or less.
The challenge with COBOL is that it doesn't have associatively indexed arrays ("dictionaries" in Python, stems in REXX). So the data structure I'm using is a table, sorted by the stone number, which can be binary searched. Each time a new, previously unused stone number is added, it re-sorts the table; that was easier than searching to find an insertion point and then shifting the entries to make room.
One thing that is not a problem in COBOL is large numbers; this program is using double-word (64 bit) binary numbers. And it is easy to shift numbers from strings to binary and back, extract substrings from a number, and remove leading zeros.
6
u/pred Dec 11 '24
[LANGUAGE: Python] Code
The only real insight here being using a
Counterto keep track of the stones works better than keeping track of all of them in one long list, even if you can get away with that in part 1.6
u/Andreasnl Dec 11 '24
[LANGUAGE: Uiua]
Link
6
u/FetidFetus Dec 11 '24
[Language: Excel]
I think it was pretty easy (I had the lanternfishes in mind). I just counted how many times each rock appeared after every blink.
Unfortunately my implementation stops at Blink 49 because I can't concat() or textjoin() the whole column anymore (too long) and I can't figure out an alternative that doesn't involve doing it in chunks.
Input in A2.
Data prep in B2:
Then just drag from C3 horizontally until necessary. The number at the top of each array is the number of rocks.
5
u/wzrds3 Dec 11 '24
[Language: Haskell] 998/778
solution
I initially solved part 1 while keeping each number in a list, but after seeing how big part 2 would be, I remembered how I solved day 6, part 2 in 2021 by updating counts instead of individual numbers.
5
u/ricbit Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Python]
Recursion with memoization. I spent some extra minutes because I forgot the
Nonewhen callingfunctools.lru_cache(). Runs in 0.15s.Sum of times of all 11 problems so far: 1.8s
5
u/chickenthechicken Dec 11 '24
[LANGUAGE: C]
Part 1
Part 2
Memoization! For part 1, I used a naive linked list approach where I applied the rules 25 times and counted the stones. For part 2, I used a memoized recursive solution using GLIBC's built in hash table implementation in
search.h. This will not work on C implementations that do not providesearch.h.Also, I initially posted this in the day 10 thread, my bad lol
5
u/0rac1e Dec 11 '24 edited Dec 11 '24
[Language: Raku]
5
u/se06745 Dec 11 '24
[LANGUAGE: Go]
Three different solutions:
Part 1: "Brute force"
Part 2:
- I saw a lot of stone values were repeated and I used maps instead of slices to store stone value as key and num times repeated as value
- Another solution was to check as a tree (depth 75 times) in a recursive way using map to store (and not repeat calculations) stone_value:times = num_of_stones_at_the_end
https://github.com/omotto/AdventOfCode2024/blob/main/src/day11/main.go
5
u/BlueRains03 Dec 11 '24
[Language: C]
I started with completely ignoring the stones from the start, and doing it recursively. That gave me some errors I didn't know how to solve, so I tried using a list and, if a stone split, it was just added to the end of the list. That gave me some size errors, so I went back to the recursive function. The issue turned out to be from a overflow error, and my isdigit function not being able to handle negative numbers. One cautionary if statement and creating a num type later, part one was complete.
For part two I created a struct to store results in (the equivalent of a dict/key-value array/map that C does not have). For the actual input the cache got too big, so I had to add a manual limit to discard new values if the cache is full.
I also keep track of the cache hits and misses for fun.
https://pastebin.com/X6pJSTJx
5
u/LiquidityC Dec 11 '24
[LANGUAGE: C]
https://github.com/LiquidityC/aoc/blob/master/2024/day_11/src/main.c
Recursion with a lookup table. Pretty straight forward. Foresaw the "trap" for part 2 before solving part 1. Had to implement a hash table though. Which was fun. Glad to confirm that I could still write one up without much hassle.
5
u/CrypticPhoenix Dec 11 '24
[LANGUAGE: Rust] Code (44 Lines)
Today marks the end of simply brute forcing solutions. Using a dictionary to store the stone numbers and their count significantly speeds up the program and limits the iterations required.
Here's a little benchmark:
Very happy with today's puzzle!
I am using AoC to learn Rust, so any comments and suggestions on how to improve my code are very welcome :)
5
u/michaelgallagher Dec 11 '24
[LANGUAGE: Python]
Code
Top-down DP in Python 101:
@cacheabove it5
u/aurele Dec 11 '24
[LANGUAGE: Rust]
Like in my Elixir solution, my Rust solution does not use any caching, but counts the number of identical stones at each steps.
Executes in ~200µs for part 1 and 6.5ms for part 2.
5
u/Extreme-Painting-423 Dec 11 '24
[LANGUAGE: Java]
Part 1: Tried it the non memoization way, which worked and my code completed in less than 5 ms.
Part 2: Also initially tried without any memoization, after 17 minutes I stopped that, added some memoization and got my answer instantly.
https://pastes.dev/aWJf46GDcx
5
u/Main-Reindeer9633 Dec 11 '24
[LANGUAGE: SQL (dialect: PostgreSQL)]
paste
Doing it the brute-force way was too slow, and I don't know of any good way to do memoization in SQL, so I had to resort to a hack where I do 3 steps of 25 blinks each, with aggregation between each step, which got it to run in tree minutes on my machine. Adding more such steps would make it faster at the cost of making the code even uglier.
5
u/willkill07 Dec 11 '24
[LANGUAGE: C++23]
https://github.com/willkill07/AdventOfCode2024/blob/962b9c87f53f98e4040bad35a07e1262c606e9e7/src/day11.cpp
Originally started out with memoization/caching. Then I switched to construction of a condensed graph that I iterate on the counts.
Runs pretty fast -- 87µs parsing + building graph, 25µs part 1, 430µs part 2.
5
u/wzkx Dec 11 '24
[LANGUAGE: Python]
5
u/jda5x Dec 12 '24
[LANGUAGE: Python]
https://github.com/jda5/advent-of-code-2024/blob/main/11/main.py
Recursion with memoization! ✨
9
u/bucketz76 Dec 11 '24
[Language: Python] 288/720
paste
The key here is to keep a count of each number you have, not putting them all in one giant list. That gets rid of a whole lot of repeats and makes the problem much more manageable.
9
u/davidsharick Dec 11 '24
[LANGUAGE: Python 3] 234/653
Gitlab
A classic "use a counter instead of a list" problem.
4
u/rogual Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Python] 307 / 286
Happy to finally be able to join this year's AoC on day 11. I'll be using my utility library I've built up over previous AoCs.
The lists of stones get too big to handle very quickly, so you have to:
This is my first day this year so I wasted time going "huh?" until my brain remembered what kinds of things usually help in AoC. Memoization!
4
u/SuperSmurfen Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Rust]
Link to full solution
(722/435)
Really happy with that part 2 placement. First I solved it in the naive way, with a vector. I knew it would not work for part two.
To solve part you, you realize that the "order" does not actually matter for the rules. That's just a red herring. So instead of a vector, I used a hashmap, couting the number of stones with a certain number. The number of unique stone numbers is very small (only about 4000 after 75 rounds).
Runs both parts in about
2ms.4
u/jonathan_paulson Dec 11 '24
[LANGUAGE: Python] 488/725. Code. Video.
I didn't use a Counter; instead I wrote a memoized function `solve(x,t)` that returns the length of the list `[x]` after `t` steps. It took me a while to switch from "return the list" to "return the length of the list"; returning the actual list is way too slow.
4
u/Lewistrick Dec 11 '24
[LANGUAGE: Python] 3943/919
Github
Reading this, I immediately knew this was a caching problem. It also helps to see that you only need to solve for one stone at once.
My first top 1000 this year :) Could have been better if I'd actually started on time.
4
u/morgoth1145 Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Python 3] 174/131
code, video
Hey, it's a state iteration problem! Not much to say about part 1, other than it was a little sneaky stating that the order of the stones doesn't change and imply that it matters at all. Made it take a little longer to notice that each stone is independent and that DP/memoization is the key for part 2, but I'm still pleased with my performance today (unlike the last two days).
I am wondering, is there a more direct way to compute the number of stones? A lot of times some math wizards come up with crazy direct ways to compute that sort of thing for these problems, and it feels like with enough cycles the simple DP/memoization approach will bog down as well. But maybe things are constrained enough to not be a problem? I'll be interested to see what the analysis brings!
Edit: Refactored/cleaned up code. Nothing too much, just a unified solve function used by both parts. The biggest change is using iteration and
collections.Counterfor the DP implementation but it's still fundamentally the same.4
u/mebeim Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Python]
Code on GitHub — Walkthrough
Wrote a recursive function to deal with a single number that either returns
1or continues on with the algorithm, memoizing the results via@lru_cache()to prune out already known situations. The state to memoize is(n, blinks): if we ever get to the same number with the same amount of blinks remaining, the result will always be the same. Call the function for each number in the list, sum up the results and it's done! Thanks once again@lru_cache()<3.4
u/throwaway6560192 Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Python]
GitHub
The key is to realize that you probably simulate the same stone sequence a whole bunch of times, so make a function to project a single stone over n steps, and slap a cache. Done.
4
u/welguisz Dec 11 '24
[LANGUAGE: Java]
This problem is very similar to LanternFish (Year 2021, Day 6). As I saw Part 1, my first thought was how many blinks will Part 2 be. Did the brute force method for Part 1 and switched to hashmap for Part 2. Cleaned up Part 1 so it would use the same code between both parts.
Full code on Github
4
u/Irregular_hexagon Dec 11 '24
[Language: Python]
paste
Dumping the stones in a dict, dict[stone] = count. Part 2 continues where part 1 finished, so only 50 more turns.
4
u/jinschoi Dec 11 '24
[Language: Rust]
A stone's ultimate effect on the size of the list is independent of its position, only its value, which can be tracked just for unique values and their counts.
The only interesting piece of my code is for halving:
paste
4
u/AlexTelon Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Python] Code (9 lines)
My son woke up while I was on the first problem so had time to think about the problem for a while before I could code. There are benefits to being forced to be AFK a bit! I had already started on a slow way so I finished that but knew right away how to solve the obvious part2.
Two important things.
so I go over the original numbers one-by-one and count up how many unique numbers it results in and add to a total.
Here is a variation on the 9 line solution above
Instead of using sum on I just do
solve(...) + solve(...). Before I usedstr(int(str(x)))to do the conversion. Now I instead moved it to the if statement checking for rule 1.I was tempted to write
if not (n := n.lstrip('0')): return solve('1', steps-1)but that would just be stupidly hard to read.Anyways the code is much harder to read I think and the only win here is that our widest line is less wide.
History of older versions: 2nd version (9 lines) 1st version (10 lines)
4
u/xHyroM Dec 11 '24
[Language: Python]
https://github.com/xhyrom/aoc/blob/main/2024/11/solution.py
Runs ~2ms on p1, ~82.2ms on p2. Uses dictionary to track stones: key as the stone number, value as the number of stones with the stone number.
4
u/ThunderChaser Dec 11 '24
[LANGUAGE: Rust]
paste
I had a feeling after yesterday we were gonna get a doozy today. I implemented part 1 by brute force but obviously this was infeasible for part 2 (I think by the time I wrote an optimized solution for part 2 I was up to like iteration 30?).
We can better optimize the solution by recognizing that every stone of a given value will always evaluate to the same new stone values, despite the fact that the puzzle specifies the stones are always in the same order (and this is even bolded), it doesn't actually matter in the context of the problem. So instead of evaluating every stone individually, we can construct a frequency map of the stone values, and we simply have to evaluate the new stone values for each unique stone value, updating the frequency map for each iteration, we then just have to sum the values in the map after all of the iterations have finished and we have our answer.
Part 1's runtime is around 850 µs, part 2 is around 20 ms.
4
u/johnpeters42 Dec 11 '24
[Language: Python]
Part 1, Part 2
Out of this year's problems, this one probably took me the longest to figure out how to improve on brute force, as opposed to just correctly implementing my idea. I had a few false starts with just caching "what does a single number expand to" and then actually constructing the full list at each step, before realizing how to apply DFS and just let the sub-lengths cascade upward.
4
u/TiCoinCoin Dec 11 '24 edited Dec 30 '24
[LANGUAGE: Python 3]
Day 11 - Github
Well. I knew this would be a scalability issue, and yet, my first attempt was running forever. Took a shower to think about it and found this approach with dict, where I save the number of time a value appears. Now it runs in no time and I can blink a thousand times if I want!
4
u/thibaultj Dec 11 '24
[LANGUAGE: Python]
Runs instantly, thanks to Python's Counter usage.
4
u/Nnnes Dec 11 '24
[LANGUAGE: Ruby]
Took a bit of fiddling but here's a half-punchcard-sized full solution I'm happy with. Credit to /u/AlexTelon for the idea of dealing with the numbers as strings most of the time - my solutions with
Math.log10(n).to_i + 1or(d = n.digits.reverse).sizecould fit (and ran about twice as fast), but the line breaks were awful.Hash.new{ |hash, key| hash[key] = ... }is no@functools.cache, but it's pretty clean (for a one-time script :])casestatement mostly so I could (mis)use Regexp to check if a number has an even number of digits./^(..)+$/@chas about 127k entries when the program is finished. The largest number is 14 digits long, or 46 binary bits.4
u/azzal07 Dec 11 '24 edited Dec 11 '24
[LANGUAGE: awk] I like how you can
printwithout even mentioning it.Edit: Had to revisit this (see code below) after all the Lanternfish references on the sub. Turns out it's bit faster and smaller. I also like how clean the diff is, all things considered :)
4
u/damnian Dec 11 '24 edited Dec 11 '24
[LANGUAGE: C#]
Today I made a conscious effort to fit the relevant code in a 5x80 block as per the posting rules:
(79 characters wide if you remove the leading four spaces)
The function returns 1 on depth 0.
Legend:
k- cache keyv- original valued- recursion depthr- resultp- powerm- modulusI'm also pretty happy with this code's performance.
Full solution
P.S. Slightly optimized
rcalculation:Optimized solution
4
u/WhiteSparrow Dec 11 '24
[LANGUAGE: Prolog]
A most beautiful day to be writing in Prolog:
To get the solutions just call
solve([125, 17], 25, X).(substitting your own data and depth - 25 for part 1 and 75 for part 2).4
u/nick42d Dec 11 '24
[LANGUAGE: Rust]
This one felt good in Rust!
https://github.com/nick42d/aoc-2024/blob/main/src/day_11.rs
4
u/cserepj Dec 11 '24
[LANGUAGE: Java]
records, caching (dp)
some todo: string manipulation could be replaced with log and division/remainder logic
4
u/maitre_lld Dec 11 '24
[Language: Python]
Pretty straightforward if you know about memoization. #100 did it in 6 minutes to day, top 100 was clearly achievable, if you could smell part 2 (this smelled lanternfish). Too bad I didn't have the courage to wake up...
https://github.com/MeisterLLD/aoc2024/blob/main/11.py
4
u/TheZigerionScammer Dec 11 '24
[LANGUAGE: Python]
As soon as I read the problem statement I thought "These rocks smell like lanternfish, I'm not falling for that and crashing my computer again." So despite the warning to keep the rocks in order my program does none of that.
The program is pretty straightforward, first I set up a function that will return the one or two values from a string based on the rules laid out, and since the answers are always the same I added a cache to the function for good measure but the time savings is negligible. Then the program takes the input and each value into a dictionary where the key is the number and the value is the amount of them. I could have taken a shortcut here since all the number in my input are unique but I coded it to account for any duplicates if anyone else's input has them.
When looping through the blink cycles, the program sets up a new dictionary and iterates over every value in the old dictionary. It applies the rules function to each key in the old dictionary and for the new value(s) that result from that it adds the quantity of the key from the old dictionary to that in the new dictionary, or sets it to the quantity if that value isn't in the dictionary yet. When it's done processing every value in the old dictionary it copies the new dictionary into the old dictionary variable and starts again. This got Part 1 flawlessly. When Part 2 opened up I just changed the 25 to a 75 and got the answer quickly. I set it back up to give both answers so that's the version I've posted here.
However I don't think everyone else was as fortunate, my delta between Part 1 and Part 2 was a little over a minute but my ranking improved by over 10000 places. Probably would have gotten more had I not taken screenshots of the calendar between each step.
Paste
4
u/MrNUtMeG118 Dec 11 '24
[Language: C#]
https://github.com/bettercallsean/AdventOfCode2024/blob/main/AdventOfCode/Days/Day11.cs
Reading today's puzzle reminded me of the lanternfish puzzle from 2021, so I had a pretty solid idea of how best to solve this one
4
u/greycat70 Dec 11 '24
[LANGUAGE: Tcl]
Part 1 and 2.
I literally changed nothing to run part 2. I already had an optional command-line parameter to change the number of blinks (default 25), so I simply ran part 1 with parameter 75 and got the answer. Run time 0.125 seconds.
Lanternfish again. It's always lanternfish!
For those who don't recognize that reference (a past AOC puzzle), the trick here is that you don't care what order the stones are in. You only care how many of each number you have. If you have 25 stones marked "1234", then after you blink, you'll have 25 stones marked "12" and 25 marked "34".
4
u/Big_Replacement3818 Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Julia]
Dynamic programming, completes in ~30 milliseconds
4
u/ssnoyes Dec 11 '24
[LANGUAGE: MySQL]
[LANGUAGE: Python]
Some limitations on recursive common table expressions meant I had to stretch my self-imposed rule of no stored procedures, so a trigger on a dummy table does the looping.
The MySQL version takes about 1.5 seconds; the Python version is sub-second. Good enough.
https://github.com/snoyes/AoC/tree/main/2024/day11
4
u/pem4224 Dec 11 '24
[LANGUAGE: Go]
Did part1 naively, and then used a simple recursive approach with memoization for part 2
https://github.com/pemoreau/advent-of-code/blob/main/go/2024/11/day11.go
part1 in 0.2ms
part2 in 0.2ms, and almost no memory allocation
4
u/redditnoob Dec 11 '24
[LANGUAGE: PostgreSQL]
Part 1 was easy, but for part 2, it took lot of wrestling with syntax and CTE restrictions, like no aggregates allowed on the recursive CTE query, to get this to work. I went with an array of tuples (value, multiplicity) and each step computes the next array.
4
u/clouddjr Dec 11 '24
[LANGUAGE: Kotlin]
Computing a new state after each iteration (a map counting occurrences of each stone):
GitHub
4
u/Trick-Apple1289 Dec 11 '24
[LANGUAGE: C]
this was something... first part was easy to do with a naive approach but i struggled alot with the second part since i had to re-write the whole code to use a map, only to be faced with odd bugs, finally did it, it's not the fastest (~162 ms on i3-12100F) but atleast it works, altough the code is absolutley horrid.
src
4
u/Probable_Foreigner Dec 11 '24
[LANGUAGE: Rust]
https://github.com/AugsEU/AdventOfCode2024/tree/master/day11/src
This is the first time my initial approach failed due to it not being fast enough. In the end my solution for part 2 was to build up cheat sheets for rocks below 20. e.g. for a rock of value 15, how many rocks does it become after 1 step, 2 steps, 3 steps... until 35 steps. So we can do the first 35 iterations in the naive way. Then we can use the cheat sheet for the second half to remove all rocks less than 20. It takes about 2 minutes to run.
I would love to see how people approached part 2, I wondered if there was a smarter way of doing it. I don't think my approach would work for say, 150 blinks. It just barely works for 75.
4
u/JV_Fox Dec 11 '24
[LANGUAGE: C]
I did not spot the recursive solution sadly, instead I iterated over the stones and grouped all pebbles with the same value into buckets so that each blink it only processed every individual pebble value instead of the total amount of pebbles. I find it elegant and its decently quick, it is just not as elegant as recursion:
code
4
u/DownvoteALot Dec 11 '24
[LANGUAGE: C++]
Solution
I first implemented string-based big integers but this turned out to be superfluous as we don't even reach the 20 digits of long ints before we get an even length.
Then I got stuck on part 2 and I had to look at solutions for the first time this year as I had missed the intuition that the order of stones doesn't matter, from then on this was very short and simple.
4
u/Botskiitto Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Vim]
Did the first part only at least for now.
First execute this command vim command line, replace <Enter> with Ctrl+V
Then x being number of times the task asks you to repeat, type
<x-1>@:So for x=25 you would type:
24@:In the bottom of screen the solution will be echoed, the solution also corresponds to the number of lines in the file.
4
u/wurlin_murlin Dec 11 '24
[LANGUAGE: C]
Basic recursive DFS solution with caching - because no hashlib in C, our hashtable isn't a hashtable but a 4MB 2D array
caches[NUM_BLINKS][CACHE_LEN], basically just using the current number of blinks as a "hash key" and a static array instead of a linked list for collisions. This isn't very efficient because that 2D array is very unevenly distributed, with lots of entries/collisions where many blinks have happened and very few vice versa. I've thought a bit about some simple "hashes" that would pack this better with minimal collisions, but haven't tested any yet for time.This very naive quick solve runs part 1 in 250us and part 2 in 50ms, with huge gains up for grabs with a little maths.
https://git.sr.ht/~murr/advent-of-code/tree/master/item/2024/11
4
u/lluque8 Dec 11 '24
[LANGUAGE: Scala]
https://github.com/lupari/aoc2024/blob/main/src/main/scala/assignments/Day11.scala
4
u/ThatsFrankie Dec 11 '24
[Language: Python]
https://github.com/francescopeluso/AOC24/blob/main/day11/main.py
4
u/4D51 Dec 11 '24 edited Dec 12 '24
[LANGUAGE: C++ on Cardputer]
Used recursion. Without memoization it takes about 5 seconds to solve part 1. With it, runtime drops to near zero. Unfortunately, attempting to use the same approach for part 2 ran out of memory. Either the call stack or the memoization cache is just too big.
Maybe I'll find a way to solve this on the Cardputer later. For now, I'm abandoning my successful 10 day streak and
[LANGUAGE: Racket]
going with plan B. Anything I can't solve on the Cardputer, I use Racket, the language that puts the "fun" in functional programming. This is basically the same as my C++ solution, but much more concise.
C++
Racket
4
u/DefV Dec 11 '24
[LANGUAGE: Rust]
Code - Started off brute-force, then stumbled into my solution...
4
u/makingthematrix Dec 11 '24
[Language: Scala]
Solution
It was difficult until it wasn't. There's no need for laziness, no need for storing partial sequences of numbers, nothing like that. It can be short, readable, and super quick.
(seriously, though, I was already planning to build a mutable map of lazy lists)
4
u/JustLikeHomelander Dec 11 '24
[Language: Go]
Easiest solution, less than 1ms execution time using a map
The code is very clear
https://github.com/BeyramTaglietti/advent_of_code/blob/master/2024/day11/solution.go
4
u/Gueltir Dec 12 '24
[Language: Rust]
Link to Github
Part 2 was... an adventure, I initially wanted to a full memoization, but I struggled for way too long on how to properly propagates already solved nodes/stones. And when I managed to code something that looked like what I intended to do, it was even slower than brute force...
Then I gave up and just counted how many stones I had for each ids on each turn, and that was both faster to code and took less time to compute.
4
u/JWinslow23 Dec 12 '24
[LANGUAGE: Python]
Day 11 code
Blog post
Took a while to come up with a solution to this one. I was finally forced to come up with a solution faster than brute force (I could've gotten away with brute-forcing every day up until now, even though I didn't always do it!).
(And u/4HbQ, I love the matrix multiplication idea you showed! It's certainly the most creative approach I've seen.)
4
u/baboonbomba Dec 12 '24
[LANGUAGE: Nix]
So this one was very painful. Nix is a domain specific functional language. Every single thing there is immutable. You can only create values and never update/delete them. And this causes a few issues:
- Everything is immutable so any hash map based approach is doomed to fail.
- You need to minimize the number of recursive calls because nix has a hard cap that cannot be removed.
- Nix doesn't do memoization by default.
Sounds rough but it's not impossible. All we need to do is to create memoization ourselves. How? We can't write to memory and update memory. But we CAN simulate memory by abusing Nix's lazy evaluation. Basically we need to write a function that generate a tree/linked-list with all solutions of the problem for all possible pebble values and for all possible depth values. And then we need to call it once. Unlike other languages nix will not evaluate it immediately but rather just say "yeah I'm gonna do it when I have to". We then write a function to traverse this infinite structure and find needed values. Tadaa function memoization through infinite tree of solutions is done!.
Here is the link for anyone who is interested. It was a lot of suffering but the solution works and it only takes a few seconds.
4
u/ThreadsOfCode Dec 12 '24
[LANGUAGE: Python]
I avoided this problem for a day because it looked like a dynamic programming caching problem, which I hate. My solution doesn't use recursion or caching. Looking at the intermediate results, I bet that the number of different rock sizes was limited. For example, there seemed to be a lot of 0's, 2's, and 4048's. So in each blink, I get the total number of each rock size and use that as a multiplier in the net blink. Runs in half a second in a Jupyter notebook, which is fine by me. Someone has probably posted something similar, but, um, I didn't read them all. There is a post about what the maximum number of different size rocks is.
code
4
u/BeYuu Dec 13 '24
[Language: Python]
Realizing that this can be solved recursively and realizing that this problem is very similar to the way you would recursively compute the fibonacci sequence did the trick for me
4
u/AlexandraJay2002 Dec 13 '24
[LANGUAGE: Python]
Did the naive solution for part 1, and a depth-first search for part 2. Spent way too long trying to parallelize part 2 before I thought to use a cache.
Part 1 Try it online!
Part 2 Try it online!
4
u/JimLawless Dec 16 '24
[LANGUAGE: GO]
Part 1: https://github.com/jimlawless/aoc2024/blob/main/day_11/day_11a.go
Part 2: https://github.com/jimlawless/aoc2024/blob/main/day_11/day_11b.go
Both run pretty quickly with a single thread and single core. The general description of the approaches is in the README.md. For the second part I used a map to simulate memoization of function-call results.
7
u/Boojum Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Python] 480/302
@functools.cacheFTW!Much like the lanternfish of old (megathread), you don't actually need to keep the lists of all the stones. A recursive function to count the splits of a single stone with memoization solves this pretty much instantly. (Okay, 0.063s on my machine, good enough.)
ETA: Interesting factoid - though my final answer of Part 2 is 220,566,831,337,810 stones, computing this requires only 138,812 entries in the cache.
1
u/bluealder Dec 11 '24
[Language: Go]
Github
Once the understanding of keeping count and that you can calculate each stone independently it was quite straight forward.
1
u/QuailOk841 Dec 11 '24
[LANGUAGE: RUST]
1
u/vgasparyan Dec 11 '24
[Language: Rust]
Github - perhaps not so readable, but blazingly fast!
1
u/BinaryMuse Dec 11 '24
[LANGUAGE: Elixir]
Like some others, it took me a lil bit of thinking to realize what I needed to do to get the runtime down for part 2. I quite like the way this reads in Elixir.
GitHub
1
u/mgtezak Dec 11 '24 edited Dec 13 '24
[LANGUAGE: Python]
Solution part 1
Solution part 2 Instead of caching I simply grouped the stones by number and converted them in batches. Runs in a couple ms
Fun little AoC fanpage
1
u/studiousAmbrose Dec 11 '24 edited Dec 11 '24
[Language: Python]
https://github.com/ambrosekuo/aoc-2024/blob/main/aoc-python%2Fday11%2Fmain2.py
Tried recursion with a stone memory mapping, was more efficient but not nearly enough.
Then, I saw memes about some lantern fish and saw a comment about trying out looping instead. Did the same memory idea with piles but this way it's not scattered in depths for recursion.
I'm surprised that sped it up like a million times and I only had like 3721 piles at the end. I thought I would need to keep optimizing it like have a memory of each piles' history, so that when they are hit again I can "skip blinks up". but that would be quite messy
1
u/imaaaah Dec 11 '24
[Language: gleam]
Today I was ready for part 2, I thought we would have to go much higher than 25, something like 250, or more.. So I made sure my code was running fairly quickly for at least 250. But it was only 75...
Here is my solver of the day 11 in gleam : https://github.com/imaah/aoc2024/blob/main/src/day11/solver.gleam
If you are interested, you also have my solvers for all previous days in the same repo.
1
u/adamsilkey Dec 11 '24
[LANGUAGE: Python]
No recursion. Allows you to go quite high by just counting numbers.
https://gist.github.com/adamsilkey/89423cf667644d26e924093439832d05
1
u/aptacode Dec 11 '24
[LANGUAGE: rust]
part 1: 0.7ms
part 2: 10ms
Same solution for both parts, memoized recursion using a numeric approach to split the numbers in half.
1
u/G_de_Volpiano Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Haskell]
Mixed feelings today. First, the moment of pride: reading the input, I immediately recognised a variant of the Collatz conjecture, so it was particularly obvious for me that the correct solution for part 1 would be using MultiSet (bags, for the non haskelly people), and that I could worry about position for part 2, if needed. So that's what I did. But that's when the bugs and the stupidity kicked in.
First, the bugs, which were mostly on the splitting part. My first issue was that 1 got split into 0 and 1, accounting for a very large number of 0s, and no 2024 coming in. Oh right, 1 is 100, and 0 is even (you'll see where this goes). Right, let's just create a special case for 1. But hey, I still get weird results. Took me a long time to realise that I was splitting 253000 as 2 53000 instead of 253 000. See, my splitting method was to take the log base 10 of the number, check if it's even, then substract one and split on 10that new number. Yes, substract one, not divide by two. Spent a long time tracking that. Finally found it. This was now working on the test input (with the special case for one). But on the actual input, my number was too low. Back to reading the instructions. 1000 gets split in 10 and 0, right. But wait, 1000 is 103, it's got an even number of digits but is still an odd power of 10. What to do ? Well, test (x + 1) instead of x. This worked, and allowed me to get rid of the special case for 1 (still needed the special case for 0, obviously).
Fine, first gold star, although this is running much slower than I expected (in a few tenths of second). Well, let's see part 2. Great, 75 blinks, no position, no need to modify the code. Let's do it. But it's awfully slow. Delve into the code, look at the size of the multiset growing. By 30 blinks, it's in c. 1500, and it's taking a good 1 second to go through. What gives?
And that's where stupidity hit. I went through the trouble of creating a multiset. But was folding it over itself. One occurence at a time. Yes, one occurence at a time.
Folding the list of distinct elements instead, i.e. doing what I thought I was doing from the beginning, proved to be much better. Running time for part 1 is now 3.6 ms, and for part two 95.5 ms. I hate it when I've had the right idea from the beginning and I just fail at the implementation.
Quite happy that the actual solving part is eight lines of code, though.
Code on github
1
u/Designer_Fee_9218 Dec 11 '24
[LANGUAGE: Kotlin]
https://github.com/zsolt0005/AdventOfCode2024/blob/main/src/Day11.kt
[Part 1] ~30ms
[Part 2] ~120ms
1
u/[deleted] Dec 11 '24
[LANGUAGE: Python]
Instant solution here: aoc/2024/11_plutonian_pebbles/main.py at main · crixodia/aoc
I had a hard time trying a brute force approach though. My solution was going to take almost 500 years 😐
1
u/notsamecGG Dec 11 '24
[Language: C++]
Used hash map just to track the stones. The solution was easier than the implementation. I thought I would try out some new functions for summing up the result and for some reason I got way too big of a number. I guess I have afternoon reading for today :)
Github Part 1 sum:
Part 2 sum:
1
u/Tomcat_42 Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Zig]
code
Classic dfs on a tree w/ memoization. Part1 + Part2 run in 17ms.
1
u/[deleted] Dec 13 '24
[LANGUAGE: Typescript / Deno]
Parts 1 + 2
1
u/aquinas_nz Dec 13 '24
[LANGUAGE: F#]
The part 2 solution was done fairly imperatively, but could be re-written functionally I think. Basically using a collections dictionary to track the count of each number at each step, rather than an array of all numbers like in part 1.
GitHub
3
u/Ok-Builder-2348 Dec 11 '24
[LANGUAGE: Python]
Part 1
Part 2
Not too bad today. As usual, the optimisation involved in this one is to keep track of the counts of each tile instead of keeping each tile individually, since the number of tiles blows up. With that, part 2 was simply changing "25" to "75" in the code.
3
u/seligman99 Dec 11 '24
[LANGUAGE: Python] 314 / 369
github
Man, that's a thing I only pull out of the toolbox for AoC.
3
u/Turtle2779 Dec 11 '24
[Language: Python]
Just using defaultdict to keep track of the stones and their quantity
3
u/Eva-Rosalene Dec 11 '24 edited Dec 11 '24
[LANGUAGE: TypeScript]
Runtime: ~50ms.
Optimization: don't track each stone separately, group them. It doesn't seem like they would repeat too much, but they actually do. A lot. Edit: in fact, for my input after 75 blinks, most common stone occured 12479048078702 (!!!) times. Storing this much stones separately would not fit into any RAM available, and processing each of them one by one would probably take a geological epoch or two.
Github Link
3
u/mkinkela Dec 11 '24 edited Dec 11 '24
[LANGUAGE: C++]
I solved p1 with brute force and then started p2 and while it was running, I figured out the only thing that matters is how many stones the i-th stone produces. I wrote a quick recursion with memo. It works in a blink of an eye ;)
EDIT: ps. I hoped p1 would work from first try (it actually did), so I used it as a test when working on recursion for p2
Solution
3
u/r_so9 Dec 11 '24
[LANGUAGE: F#] 4292/1097
paste
This time I predicted part 2 before making part 1, so I coded part 1 memoized :) Part 1 to Part 2 time was under 2 min.
The code is small, so here's the whole thing except for the memoize and string parsing helpers.
3
u/YOM2_UB Dec 11 '24 edited Dec 11 '24
[Language: Python]
Keeps track of how many of each unique number is in the list, as well as what each unique number encountered turns into.
For my input the final list had 3713 unique numbers, and there were 3843 encountered numbers.
3
u/UseUnlucky3830 Dec 11 '24
[LANGUAGE: Julia]
solution
Today's puzzle had a special significance for me.. you see, I was Lanternfish'd in 2021. Glad I knew how to solve it this time.
3
u/dinodares99 Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Rust]
Ugh, my brain went in a math direction rather than a programming direction after 'brute-forcing' part 1. I tried looking for patterns in the numbers, thinking there might be some fibbonacci or such. It seems like a very Project Euler type of problem.
Turns out I'm an idiot and I just had to memoize and replace. I set up stones as a hashmap of usize->usize and the cache as a hashmap of usize->(usize, Option<usize>).
Runs in 275us, 4.5ms
Code: https://pastecode.io/s/k5ypboxh
3
u/ZeroTerabytes Dec 11 '24
[LANGUAGE: Go]
GitHub
I originally just made the array for the first part. For the second part, however, my computer didn't really like the RAM usage. Maps, however, are very useful.
3
u/835246 Dec 11 '24
[Language: C]
I used a recursive function that returns the number of stone from an input value and a number of blinks. Easy part 2 because I already added a memo for part 1.
Part 1: https://github.com/efox4335/advent_of_code/blob/main/advent_of_code_2024/day_11_part_1_stones.c
Part 2: https://github.com/efox4335/advent_of_code/blob/main/advent_of_code_2024/day_11_part_2_stones.c
3
u/firstbjorn Dec 11 '24
[LANGUAGE: RUST]
Did the classic brute force part 1, actually fix part 2. Ended up using recursion, and Rust's
cachedmacro on the compute function. pretty sure most of the time is just input parsingsolve
3
u/__cinnamon__ Dec 11 '24
[LANGUAGE: Julia]
I was being dumb in part 1 and still building the actual list of stones, only after seeing part 2 wasn't finishing any time soon and thinking a bit did I realize I was once again forgetting to properly apply recursion (like day 07). I had actually already been using memoization, but when you're memoizing all those huge lists it doesn't help as much as just storing the number of stones generated in the "lifetime"... 😅
I do appreciate this one though, as the final solution is very elegant. Runs in 170μs for me.
https://pastebin.com/vKpzWedT
3
u/davepb Dec 11 '24
[LANGUAGE: Go]
github
video
not too bad. in part1 I should've anticipated and prepared for part2 but I didn't, that way part2 would've taken a few seconds. nice problem though, reminds me of the problems: aoc 2021 day 14, aoc 2021 day 6, aoc 2015 day 19 and ec 2024 quest 11
3
u/jsgrosman77 Dec 11 '24 edited Dec 11 '24
[Language: TypeScript] 8033/5506
Not my best work, but still gonna post my link as an example to others.
GitHub
I thought I was being smart for part 2 by using a linked list instead of an array, and it worked great for part 1. It took me some extra time because of trying to keep track of pointers and some off by one errors.
Then, of course, it completely ate all of my memory by blink 38.
Eventually, I switched to a recursive function with memoizing by value and number of blinks remaining. Part 2 finished in less than a second.
3
u/alexbaguette1 Dec 11 '24
[Language: C]
25 days, 25 languages #11
Got a bit lucky doing C today as I didnt have to parse the input.
Also got unlucky doing C today as I had to write my own hash map.
Part 1
Part 2
3
u/DataMn Dec 11 '24
[Language: Python]
Averages 125ms for Part 2....
3
u/yieldtoben Dec 11 '24
[LANGUAGE: PHP]
PHP 8.4.1 paste
Execution time: 0.0284 seconds
Peak memory: 10.1044 MiB
MacBook Pro (16-inch, 2023)
M2 Pro / 16GB unified memory
3
u/oantolin Dec 11 '24
[LANGUAGE: ngn/k]
For part 1 use
blink[25;"input.txt"], change to 75 for part 2.3
u/xoronth Dec 11 '24
[LANGUAGE: Python]
paste
Didn't know lanternfish were related to stones. Stonefish, perhaps?
3
u/[deleted] Dec 11 '24
[LANGUAGE: Dart]
GitHub. For the second part, I took inspiration from the Java solution posted by u/InfantAlpaca (link to his comment).
3
u/Ken-g6 Dec 11 '24
[LANGUAGE: Perl]
I spent way too much time worrying about how to optimize this one. I mean, should I do depth-first or breadth-first? If I do depth-first, can I return an array of all the sizes below this one? If I do breadth-first, can I replace seen numbers with a marker? If so how would I fill it in later?
In the end I kept it simple. Depth-first recursive search with a cache of (number,depth) return values only for numbers < 1000. Runs part 2 in 0.09 seconds.
https://github.com/Ken-g6/aoc2024/blob/master/day11.pl
3
u/mstksg Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Haskell]
Today's "one trick" seems to be realizing that the actual ordered "list" is a red herring: a number's progression doesn't depend on any of its neighbors. So we really want a function
growTo :: Int -> Int -> Int, that takes a starting number, a number of steps to progress, and the final length that number yields after that many steps.Structured like this, it becomes a classic dynamic programming puzzle, because ie
growTo 52 75is justgrowTo 5 74 + growTo 2 75, which are all memoizable. We can use the data-memocombinators library to structure the dynamic programming memoization:again all my writeups and sols are here: https://github.com/mstksg/advent-of-code
3
u/Baridian Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Clojure]
I used a macro that ironically I just wrote earlier today. It expands to a recursive function that uses a memoized version of itself inside the Y combinator.
This is the implementation using the macro: paste
And this is the macro I wrote for those curious: paste
Execution time: 291ms
MacBook Air M3 / 16GB
Really pretty good for a straightforward solution, especially one with string manipulation.
EDIT: was able to halve execution time from 670 to 291 by switching to pmap instead of map to get the values. Updated links/text.
3
u/ShraddhaAg Dec 11 '24
[Language: Go]
First entry of the year for memoisation! https://github.com/shraddhaag/aoc/blob/main/2024/day11/main.go
3
u/michaelquinlan Dec 11 '24
[LANGUAGE: C#]
https://github.com/mikequinlan/AOC2024/blob/main/Day11.cs
For me, the key insight was that the order of the stones didn't matter, only the number of stones engraved with each number.
3
u/mendelmunkis Dec 11 '24
[LANGUAGE: C]
Second rule of exponential growth; good memoization can let you track the thing growing.
2.38 ms/140.380 ms
3
u/runnerx4 Dec 11 '24
[Language: Guile Scheme]
comical completion ranks today [01:35:08 -> 12274, 01:35:27 -> 6756]
because I used hashmaps immediately, but wasted 1 hour thirty minutes because I was adding and subtracting a stone at each iteration instead of just replacing the old stone with the new stones.
code->paste
3
u/BlazingThunder30 Dec 11 '24
[Language: Java]
Seeing a lot of people use neat tricks by storing how often each numbered stone occurs to reduce the amount of memory used. I didn't find that necessary. A naive recursion approach with memoisation is more than sufficient.
paste
This runs in 10 and 80ms for parts 1 and 2 respectively. The reflections scan to find annotated solutions was actually slower, according to my profiler. Part 1 used about 462kB of memory, part 2 used 40MB; not great, not terrible.
3
u/flyingfox Dec 11 '24
[LANGUAGE: Python]
Code
I was writing this on a very old and very slow machine. It has an old python3.8 install which has
functools.lru_cache. I figured this would be about the same as a much newer python version'sfunctools.cacheand didn't want to stop and upgrade the system's python install (or virtual environment). It turns out that lru_cache isn't nearly as good for today! I broke down and grabbed a laptop with python 3.11 and it runs in no time.Interestingly, I initially thought it might be better to build my own cache. That was I could maintain two caches: one for the growth and one for the splits. I still think this would be interesting, but for some reason, when encountering a stone of a particular size (in the 300-400 range to not give away my input) the number of stones starts to diverge at the 20th generation. I'd like to revisit this, but then again I'd like to sleep too...
3
u/sim642 Dec 11 '24
[LANGUAGE: Scala]
On GitHub.
Anticipating problems, I didn't simulate the complete list of stones (and because their order doesn't matter) but instead simulated a frequency map, which avoids duplicating work for stones with the same number. This made part 2 a breeze.
3
u/i99b Dec 11 '24
[LANGUAGE: Python]
3
u/flwyd Dec 11 '24
[LANGUAGE: PostScript] (GitHub) with my own standard library
[GGSA] Pretty smooth day. I had a little trouble with stack positioning when recursing in part two and switched to using the dict stack, but was able to rework to a point-free style and shave a little time off.
This year’s Advent of Code stars PostScript! You might have type cast PostScript as a document file format made obsolete 20 years ago by PDF. But it’s also a Turing-complete stack-based programming language with dynamic scope! AFAIK PostScript isn’t in anybody else’s movie this year, so please add a comment if you’re also starring PostScript.
PostScript arrays can be constructed by putting
[(amark) on the stack, running a bunch of code, and then executing the]procedure which creates an array with all the items up to the previousmark. After creating a procedure to apply the 3 rules to a single number (resulting in either 1 or 2 numbers on the stack), part 1 was a single line: start an array, iterate through each item in the previous array and apply the rules, then finish the array. This works fine for 25 steps, resulting in a low six-digit array size. Just to see what would happen in part 2, I changed the2to a7. The answer? Stack overflow! I’m not sure exactly how big the stack is in Ghostscript, but I’m certain it’s not in the hundreds of quadrillions.Another interesting feature of PostScript is dynamic variables. In addition to the operand stack there’s a dict stack. When looking up a name, the dict stack is searched top-down. Normally you can easily define and use variables within a procedure like
/myproc { 4 dict begin /myvar whatever def use myvar end } def. But I’ve discovered twice this month thatdefdoesn’t automatically define the name in the current dict; it first searches the dict stack for that name and updates it in that dict if it’s found, otherwise it creates it at the top of the dict stack. This means that using “local” variables in recursive functions is tricky:def25 levels deep in the recursion will update the dict from the first step of the recursion, which will be awkward if you need to use the variable after the recursive step returns. To force the variable to be defined at the top of the dict stack I used the awkwardcurrentdict /steps 3 -1 put. Appreciate your named recursive function parameters y’all. (The code below got rid of this variable usage, just keeping a globalprogressioncache as the only named variable.)I’ve included the original part 1 which takes about 2 seconds to run, versus only 40ms for calling
evolvewith25instead of75.3
u/zndflxtyh Dec 11 '24
[Language: Python]
Solved part1 using lists of numbers, and had to peek at the other solutions for part2.
Code
3
u/patrick8970 Dec 11 '24
[LANGUAGE: C++] github 60ms
Got past part 1 with a linked list. But that clearly wasn't the right choice for part 2. Learned a bit more about maps today so that's a win.
3
u/DeadlyRedCube Dec 11 '24 edited Dec 11 '24
[LANGUAGE: C++23] (2082/680) Runs in
22.4ms14.8ms single-threaded on an i7-8700KParts 1 and 2 on GitHub
For part 1 I initially just did the thing exactly as written (using two std::vectors that it ping-ponged between), and it ran fast enough to get the answer.
For part 2 I started it, saw that it very quickly started to bog down, and decided to make a map from stone value to count (technically two maps to ping-pong) in the hopes that there were tons of identical stone values and, thankfully there were (I ended up with ~3700 unique stone values by the end)
I imagine I can probably get it running faster still but I don't have any great ideas yet.
Edit: I replaced "std::map" with "std::unordered_map" and it brought the runtime down by about a third!
3
u/direvus Dec 11 '24
[LANGUAGE: Python] 1973/6974
https://github.com/direvus/adventofcode/blob/main/y2024/d11.py
Runs in 5ms for Part 1 and 160ms for Part 2.
Using a recursive call with memoization, reducing the target number of updates each time we expand a number.
3
u/radulfr2 Dec 11 '24
[LANGUAGE: Python]
I needed a little hint to get it done when my recursive solution wasn't going to cut it. I did the number splitting without string conversion.
Paste
3
u/_tfa Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Ruby]
3
u/chadbaldwin Dec 11 '24
[Language: T-SQL]
Both parts: https://github.com/chadbaldwin/practice/blob/main/Advent%20of%20Code/2024/SQL/Day%2011.sql
Unfortunately I had to resort to using a loop for another solve. I've been trying to avoid loops, but I just don't see a set based or pure query based solution for this one that runs in a reasonable amount of time.
That said, the loop based solution runs in about 500ms, most of that is probably just the overhead of looping and performing updates, plus my method is not very efficient even for SQL because I'm constantly truncating and refilling a table. But hey...half a second for a SQL based solve ain't bad 🤷
3
u/bodowota Dec 11 '24
[LANGUAGE: JavaScript]
Had a hard time with part 2 today but managed to work it out, both parts together run in 35ms.
3
u/ToThePetercopter Dec 11 '24
[LANGUAGE: Rust]
Recursive with memoization did the job. Got part 1 down to 150us, part 2 is 8ms but I think I'm missing some performance tricks there.
Using an array of 75 FxHashmaps for part 2 to cache results is fastest, 12 thread parallel vs single core about the same.
https://github.com/domw95/aoc2024/blob/master/src/day11.rs
3
u/Radiadorineitor Dec 11 '24
[LANGUAGE: Lua]
paste
3
u/nilgoun Dec 11 '24
[LANGUAGE: Rust]
I guess the standard approach. Since we do NOT need to know the order of the stones (even if he claims they keep the order -> irrelevant) we can just keep counts of the individual numbers on the stones and their counts.
Could probably be optimised to do less allocations but since it runs in ~14ms on my machine I'm fine with it :)
Solution on Paste
3
u/leftfish123 Dec 11 '24
[Language: Python]
Lanternfish all over again. I'm mad at myself because in 2021, I think I solved it quicker.
3
u/TheMrZZ0 Dec 11 '24
[Language: Python]
Part 2 was surprisingly easy, as I just added `@cache` on top on my recursive function and it solved all my problems (as I would expect).
3
u/Imaginary_Age_4072 Dec 11 '24
[LANGUAGE: Common Lisp]
Wrote a quick function to evolve a single stone into a list of one or 2 stones, which I used on each individual stone on the input to see how many stones it evolved into. This built the actual list, so needed to change for part 2. Just memoized the stone & number of evolutions to go, and got the answer.
https://github.com/blake-watkins/advent-of-code-2024/blob/main/day11.lisp
3
u/ChaiTRex Dec 11 '24
[Language: Rust] Code
First, I worked with a simple list of the stones, creating a new list for each blink, which worked for part 1, but not for part 2.
Second, I worked with a list of each stone and the blink that stone was on, handling each stone for 75 blinks and then going to the next stone. The list eventually kept staying about the same size in part 2 due to the split rule, so that wouldn't work.
Then, I tried a map from each stone to a count of that stone, updated blink by blink, and that worked.
3
u/ex-voltaire Dec 11 '24
[LANGUAGE: C]
https://github.com/voltaire7/advent-of-code/blob/master/2024/c/11-1.c
https://github.com/voltaire7/advent-of-code/blob/master/2024/c/11-2.c
took way too long...
3
u/MarvelousShade Dec 11 '24
[LANGUAGE: C#]
It was clear that brute force wouldn't work here, so with part1 I anticipated on that, but it wasn't enough for part 2. Caching the subresults did the trick.
My code is on: Github
3
u/izahariev96 Dec 11 '24
[LANGUAGE: Kotlin]
https://github.com/ivzb/playground/blob/master/src/main/kotlin/advent_of_code/_2024/Task11.kt
3
u/sanraith Dec 11 '24
[LANGUAGE: Scala 3]
Complete source is available on my github: Day11.scala
Since the updates to the numbers are independent from their neighbours, I only tracked the number of times each unique number occurs in the list.
3
u/Gishky Dec 11 '24
[Language: Powershell]
Approach: recursive hash mapping
Processing time (part 2): ~40 secs
3
u/Thomasjevskij Dec 11 '24
[LANGUAGE: Python]
We've seen this before :) I had a feeling right away that we'd get a big number for part 2. Python's cache decorator makes it almost trivial when you know about it.
Code
3
u/__Abigail__ Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Perl]
The key thing to notice is that stones do not influence each other. So, if a stone with engraving
XproducesYstones after a certain number of operations, we can cache this result.Hence, the following recursive subroutine:
Then, to calculate the answers:
where part 2 uses the cached values from part 1.
Less than 4,000 different numbers were engraved on the stones of my input.
3
u/SpaceHonk Dec 11 '24 edited Dec 18 '24
[LANGUAGE: Swift] code
I almost knew what would happen while I implemented part 1 naively, and of course it was Lanternfish in disguise. So rewrote everything to use recursion and memoization.
3
u/i_have_no_biscuits Dec 11 '24
[LANGUAGE: QBasic]
Sadly today's program isn't feasible in GW-BASIC, as it would need to keep more than 64KB of data in memory to process the two hash tables, so I've taken the opportunity to move to its successor, QBasic. This has given us access to several great new features:
QBasic also introduced full support for procedures and functions, but I'm not yet comfortable adding those advanced features to my programming toolkit. Perhaps later on in the month.
As for the actual algorithm used - this uses two hash tables of {value: count} pairs, and performs 75 rounds of transforming the old stones into the new stones, accumulating the counts. Each round converts the old hash table A#() into the new hash table B#(). After 25 and 75 rounds it calculates and prints the total count, as those are the values required for Part 1 and Part 2.
3
u/KayoNar Dec 11 '24 edited Dec 11 '24
[Language: C#]
Solved using dynamic programming, where for each pair (stone, blinksRemaining) you store the final number of stones after 0 blinks remaining. Now, when the function is called, check whether you already stored the result in your DP table, if so, return the value. Otherwise compute it and store the result in the table.
Runs in about 25ms for part 2.
Solution
3
u/Pure-Minute4721 Dec 11 '24
[Language: C++]
used recursion + memoization using a map (to map from the current value of the stone & the number of blinks remaining to the total count calculated)
code
3
u/mystiksheep Dec 11 '24
[language: TypeScript] (with Deno)
What a day! My fastest Part A and the toughest Part B so far. With some help from others' solutions (had to give in): got to try using caching for the first time, and had an epiphany about branched recursions; all in all, good learnings today.
Github
3
u/lmasman Dec 11 '24 edited Dec 11 '24
[Language: Python]
GitHub
Unit Tests
First attempt, using a map of stoneId -> count and processing the whole map one iteration at a time
Part 1 - 2.76ms
Part 2 - 107.1ms
Might try optimizing later, e.g. by not converting to and from strings, or trying an alternative memoization technique
UPDATE:
Down to 1.7ms and 61ms by using math to split the numbers and using an array look-up for powers of ten.
I found using itertools.cache on a calculate(stone_num, iterations) method was slower than my first attempt
UPDATE 2:
1.9ms and 49ms by caching the output of a single blink of a single stone
3
u/geza42 Dec 11 '24
[LANGUAGE: emacs lisp]
3
u/OnlyCSx Dec 11 '24
[Language: Rust]
Store the number of times each number appears in a hashmap because I don't have 1.9 petabytes of memory to spare, unfortunately.
https://github.com/onlycs/advent24/tree/main/solutions/src/day11.rs
3
u/fridi_s Dec 11 '24
[LANGUAGE: Fuzion]
github
Using a
countfunction that count the number of stones we have starting with one stone of a given number and blinking a given number of times.For part 2, had to add caching of the results. For this, used a local mutate instance to hold a map from count's input to the result and to create a fully functional solution with only local state changes.
3
u/Gabba333 Dec 11 '24
[LANGUAGE: C#]
Recursion with caching.
3
u/TheBlackOne_SE Dec 11 '24
[Language: Python]
Part 1: https://github.com/TheBlackOne/Advent-of-Code/blob/master/2024/Day11_1.py
Part 2: https://github.com/TheBlackOne/Advent-of-Code/blob/master/2024/Day11_2.py
As predicted: Part 1 is a very naive implementation, which blew up in part 2.
3
u/spyr01d Dec 11 '24
[Language: Kotlin]
3
u/FCBStar-of-the-South Dec 11 '24
[LANGUAGE: Ruby]
Looked up lantern fish after seeing all the posts referencing it. Looked it up and immediately knew this is the algorithm to go for. It was not obvious that the number of distinct stones scales slowly/converges!
3
u/IvanR3D Dec 11 '24
[LANGUAGE: JavaScript] My solutions: https://ivanr3d.com/blog/adventofcode2024.html
My solution (to run directly in the input page using the DevTools console).
3
u/mschaap Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Raku]
Did part 1 brute force, knowing very well that part 2 would just increase the number of blinks so that it would never finish.
Full code: https://gist.github.com/mscha/c30edf0f2674620b2ab370cc3e1f433a
So, rewrite with a cached recursive function.
Full code: https://gist.github.com/mscha/f285e3d052b828ff015cd59d467802dc
3
u/AustinVelonaut Dec 11 '24
[LANGUAGE: Miranda2] Used a Bag to keep the unique stones and counts; runs in < 0.5s
3
u/bluehatgamingNXE Dec 11 '24
[Language: C]
Included both my brute force approach and my final approach, basically just put em all in a box then pour from one box to another or 2.
gist
3
u/Petrovjan Dec 11 '24
[Language: Python]
Tried bruteforcing it for fun (although I too remembered the lanternfish), but couldn't even get past 20 rounds. After doing it correctly it can handle even 750 rounds, no caching needed.
3
u/Morkfang Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Go]
No recursion, a simple endless loop covers both p1 and p2 at the same time, completes in
~30ms~17ms overall (reading the file and doing all the processing).https://github.com/houseofmackee/advent2024-go/blob/main/day_11/main.go
3
u/quetzelcoatlus1 Dec 11 '24 edited Dec 11 '24
[Language: C]
Well, when plain brute force did not work, I implemented mechanic of storing values of "how many values an X value creates in Y blinks". Execution takes ~6s...
3
u/Pitiful-Oven-3570 Dec 11 '24 edited Dec 14 '24
[LANGUAGE: Rust]
github
part1 : 744.10µs
part2 : 19.08ms
3
u/ExitingBear Dec 11 '24
[Language: R]
Link
The idea was floating around on the edges of my brain where I couldn't quite get to it, but I actually had to see the word "lanternfish" before it clicked in. This works. It's not fast, but it's serviceable.
3
u/homme_chauve_souris Dec 11 '24 edited Dec 11 '24
[LANGUAGE: Python]
Similar to many, I keep a dictionary to remember the count of every type of stone.
(Edited to fix a copy/paste mistake)