5
votes
Day 9: Disk Fragmenter
Today's problem description: https://adventofcode.com/2024/day/9
Please post your solutions in your own top-level comment. Here's a template you can copy-paste into your comment to format it nicely, with the code collapsed by default inside an expandable section with syntax highlighting (you can replace python
with any of the "short names" listed in this page of supported languages):
<details>
<summary>Part 1</summary>
```python
Your code here.
```
</details>
TypeScript
Like others, I found the problem straightforward but tripped over some bugs, mostly off-by-one and loop boundary errors.
I wonder if there's going to a follow-up to this one, because even the Part 2 process is pretty sub-optimal...
Spoilers
The problem divided itself neatly into three discrete steps: Convert map to block structure; Perform compacting (different algorithm for each part); Calculate and return the checksum. I don't think the conversion and checksum steps were particularly interesting so I'll focus on the compacting.
The algorithm for Part 1 was easy enough: Walk from the left looking for empty blocks. When one is found, walk from the right looking for non-empty blocks. Then swap the two. I opted to alter the array in place rather than passing around clones of it; I don't generally like that sort of impure function because it's less readable, but in this case keeping track of everything in TS/JS would've been a lot of unnecessary mental overhead for me.
I started approaching Part 2 the same way but eventually realized I'd need to reverse the order of the loops (walk backward to find file blocks first, then walk forwards to find empty spaces that will fit them). I realized that way too late and it took some heavy debugging to figure out the problem, then I had to perform surgery on my nested looks to restructure them without mangling any of my logic.
I typically try to use descriptive variable names, but when I'm just iterating through an array I use
i
as my index. Similar nested loops will getj
and rarelyk
as seen in today's code. Once it reaches that point it's kind of a brainfuck to keep track of which is which but I don't have a better convention to use.Parts 1 and 2 (TypeScript)
Really enjoyed this one! Performance wasn't great at first on part 1, but I was able to mostly solve that problem by figuring out a better method of detecting gaps between files. I think my code here is a little messy, but I'm not really in the mood to refactor...
Solution (Jai)
For how simple the task seemed, this took an exorbitant amount of time to get right.
Process
At first I didn't want to materialize the disk in memory, because I was fairly sure that part 2 would be something like "now don't use single digits but runs of six digits to denote the ranges". That took up a long time unnecessarily. Part 1 was easy after deciding to materialize the disk, but part 2 took a very long time until all edge cases were handled... I think this was the worst puzzle so far for me, from a time-taken to enjoyment ratio.
Part 1 (Rust)
Part 2 (Rust)
The algorithm basically works by scanning forward from the start for blanks, and then backwards from the end for something to fill them with. Then I just move the file into position. If the file is smaller than the blank space, I add the remaining blanks back to the front of the scan -- this works because the original array is contiguous and alternates between files and blanks -- or maybe it doesn't and the input is just benign, I'm too tired to fully analyze it now.
Benchmark
Didn't run into too many problems with the solution itself, but lots of bugs along the way. Before coming up with a better solution I was trying to just get a slow version working inserting with
insert:before:
, not realizing that it's a private method and doesn't actually allow for insertions...Both reasonably efficient (~0.01 and ~1 seconds), but I'm still thinking of faster approaches for part 2.
Part 2 Spoilers
Since all the gap sizes are 1-9, I suspect you could speed things up by storing separate lists of each size, sorted by position in memory. Then just take the head of each list from the needed size and above and pick the one with the minimum position. I haven't implemented this, but I think it should work and reduce the time substantially.
Pharo Smalltalk Solution
My solution for part 1 basically followed the way AoC walked through the solution so it was suuuuuuper slow but it got me the right answer. I ended up going with a pretty different approach for part 2 that was significantly faster, and if I feel like it maybe I'll refactor my part 1 solution based on part 2.
I'm feeling more comfortable with Python. I started using type hints to help me out with this one. I'm sure there's still some Python-y things I'm not doing or doing the "wrong way" but it's a process. Baby steps.
Part 1 | Part 2
Behold, two abominations of pattern matching: the twin beasts,
relocate_file/2
andclear_file/2
.Look On My Works And Despair!
Both parts (Elixir)
(If you think it's incomprehensible now, you should have how it looked before I defined the
hp
macro)Benchmarks
Part 2 is slow, but I'm sick of thinking about disk defrag so I'm calling it a day. After doing some ahead-of-time indexing, I got it down to about 6s. My Python solutions:
Part 1
Part 2
Took me longer to get around to doing it than I expected.
Had to fiddle with part two a bit to get it to complete in a reasonable time because of Rhai's performance.
Rhai Solution