π - 2025 DAY 5 SOLUTIONS - π
Day 5: Cafeteria
Megathread guidelines
- Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
- You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL
FAQ
- What is this?: Here is a post with a large amount of details: https://programming.dev/post/6637268
- Where do I participate?: https://adventofcode.com/
- Is there a leaderboard for the community?: We have a programming.dev leaderboard with the info on how to join in this post: https://programming.dev/post/6631465
Javascript
Short and sweet. Basically just handle all the range overlaps for part 2 and then do basic subtraction to get the answer.
::: spoiler spoiler
:::
Haskell
IntSetwas the wrong first choice for part 2 :3Uiua
It took me a while to work out the merging of ranges, but I'm very pleased with this solution.
::: spoiler merging ranges Merging all the ranges first makes both parts faster. Sorting the ranges first means that you only need to compare adjacent pairs of ranges to decide whether they need to remain separate, or can be merged. If the end of the next range is < end of this range you can drop it; if the start of the next range < end of this range replace this with a new range of (start of this range, max of both ends); otherwise just add next to the merged list and repeat.. :::
(EDIT: I realised that Part1 was really slow, so rewrote it, and total time dropped from 140ms to about 4ms, which is nice.)
Run it here
It turns out that merging isn't necessary, it's simpler and slightly faster to just sort by starts. Part 2 then needs to remove any overlaps between adjacent ranges by setting end of each to min(end of this, start of next-1).
Python
Not too bad though part2 could become difficult if you don't realize the simple method to merge overlapping intervals.
::: spoiler see code
:::
Kotlin
I first solved the puzzle with my own horrible range-merging algorithm, had quite a few nasty off by one errors. I quickly replaced it with the "normal" merge-algorithm after getting the right solution.
If you really want to see my abomination, it's still in the commits on my repo.
::: spoiler Solution
:::
full code on Codeberg
It's amusing just how similar our solutions are today. The only real issue I had today was not considering both sides of each range and only taking the last part of the range in the sort order.
Well, I guess I've decided to do AoC this year. Trivial problem, though I shamelessly stole some range-merging code from AoC 2022 (it was when I was still well in the learning phase of Rust). If you can't look at old code and wonder WTF you were thinking, have you really gotten any better?
::: spoiler Solution (mostly uninteresting)
:::
::: spoiler
combine_rangesWARNING : Hideous, triggers Clippy, not blazingly fast:::
Nim
Huh, I didn't expect two easy days in a row.
Part 1 is a range check. Part 2 is a range merge.
Runtime: ~720 Β΅s
Full solution at Codeberg: solution.nim
Kotlin
I like this one. The first part could be solved trivially with a naive approach. The second part is also not too complicated.
I started out with a simple merging algorithm that would check all ranges, except the one to merge, for overlaps.
With clever sorting, I can skip the search for mergable ranges and just iterate in reverse and build larger and larger ranges.
I haven't tried to merge adjacent ranges like 1..2 and 3..4 to 1..4. The solution is fast enough already when JIT/C2 compiled (200-250 Β΅s).
Code on Github ::: spoiler Code
:::
Janet
I honestly didn't expect to one-shot part2, I'm usually pretty bad with coming up with complex solutions and coding them up in first-try :P
Rust
Took me way longer than it should have to work out pt2, got tripped up by a
<instead of<=.Go
I started by not sorting the ranges in the input stream and try to merge everything together. It didn't work, but the same algorithm with sorted input does. I guess I'm missing something important, but I tested my code and can't find the corner case that make it fail. Bah...
::: spoiler day05.go
:::
Futhark
As always with futhark, first the script that converts the input into futhark-format:
::: spoiler sed script and example output
this converts the example to this:
:::
I ended up writing a RangeSet implementation, currently considering publishing this as a library later. The RangeSet-Implementation makes it very large, but futhark makes it run blazingly fast π . /s
Some fiddling with existential size types made me learn the type system even better.
I discovered that futhark has a
multicore-backend, which makes it run faster. :]::: spoiler Solution with RangeSet implementation
:::
(Browser-based) Javascript
The good ol' range merge is back! I've done the past few years in Rust, which has first-class "support" for inclusive ranges, so I was dreading having to re-implement things. Turns out when you can ignore lifetimes things get simpler.
::: spoiler Code
:::
Again part 2 took me way longer than I would've liked and also than feels appropriate for the simplicity of the solution I finally came up with.
Turned out quite fast, thanks to the ranges.
Python
That is nice and simple, power of python I guess. How quick was the pt2 solve? I could imagine that being pathalogically slow with the wrong ordering of inputs?
Eg: (99,100),(0,1),..., (95,96), (96,97), (97,98), (98,99)
I haven't timed it, but easily below a second.
Could that be optimised? Most certainly.
Due to the ranges being in a set, rather than a list, the input order doesn't matter anyway. And the set really does a lot of heavy lifting for making the code so concise. You'll need a bunch of boilerplate for list maintenance, especially if you continuously keep it sorted.
The set also removed 8 duplicates I had in the input.
Rust
Tried to outsmart part 2 by not merging ranges but just subtracting overlapping ranges from the current range's size, but then overlapping overlaps are subtracted more than once. So I ended up merging the ranges. They are kept in a list that is sorted by their starting position. Also, transforming the inclusive ranges in the input into exclusive ranges made things quite a bit easier.
View on github
Rust
I didn't look at the input at first so tried a naive version with sets, but it was too slow even for part one. I got smarter and wrote a version with less code that runs in under half a millisecond.
::: spoiler Full code
C
Repo
Sweet one. Glad the merge could be done with one n^2^/2 scan and no sorting or moving, which will make the assembly port a bit easier. Speaking of which, I'm still at day 3 there, fighting not the puzzles but the 64K .COM file limit!
::: spoiler Code
:::
There's an alternative approach to merging ranges. Put starting and ending points into a heap then scan it, keeping track of the number of open ranges present. Something like this: