14
votes
Day 7: The Treachery of Whales
Today's problem description: https://adventofcode.com/2021/day/7
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>
Python golf, day 7. I'm liking the trend of these past two days' problems.
Here I use
exec()
to parse the input concisely, which you should probably never ever do in non-golfed code.Part 1
Part 2
This was a nice fun one, I tried to get clever at first with an approach that happened to work on the test input but not on the real one. But there's only 1000 numbers in the input, and the max (at least for my input) was only 1955, so the space is small enough that a simple brute-force approach works just fine.
Part 1 Ruby
Part 2 Ruby
The only change from the first version is that instead of just taking the abs/distance for each crab-ship to the prospective target, we have to find that distance and take the sum of all digits up to that value. I forget the name for it, but that always takes the form
n * (n+1) / 2
so it's just as easy to compute, no extra loops or conditions.I did have to increase my initial
best_fuel_use
value from999999
to9999999999999
:p (Ruby will automatically promote its basic integer typeFixnum
if a value gets too large, so there's not a convenienti64::MAX
or equivalent lying around.)This could be made more efficient by splitting up the search space, starting at the mean value and then working up and down from there in a gradient-descent fashion, but that wasn't necessary for the provided input.
Edit: I went back and golfed P2 down a bit just for fun
Part 2 Ruby Golf
More or less the exact same approach, just less readable :P
I did switch to using
1/0.0
which evaluates to a floating point +Infinity value which works just as well as9999999999999
in rather fewer characters, as well as replacing the plain loop with areduce
over the range so that I can return directly from there without the extra variable.I love this visual.
Unlike yesterday's problem, optimization here is helpful but not necessary so it's fairly straightforward overall.
Part 1
Part 2
I looked up the n-th triangle number formula, or "factorial but with addition" in my specific search terms as I knew that there would be a simple and established solution for it. However, brute-force looping over the range also works; it takes a few minutes but it completes and returns the correct answer.JavaScript
Repo Link
Decided to start with JavaScript first today, rather than doing it in Python and then translating.
Part 1
I had done a similar problem in the past, so I knew that the optimal position would just be the median of the given positions. To compute this, I just sorted the numbers and took the item in the middle. From there, it was just a matter of computing the fuel costs. The main thing that tripped me up was that JavaScript sorts everything alphabetically... even an array of numbers... unless you specify a custom comparison function. Pretty terrible.
Part 2
For the second part, I couldn't think of anything clever, so I just brute-forced it (ie. try every possible position and see which one yielded the minimum fuel cost). My
distance
function initially used a while loop to compute the sum from1
tod
, but then I remembered there was a formula for this (and looked it up online).With this, I learned about JavaScripts
Math.max
andMath.min
functions, along with theMath.SAFE_MAX_INTEGER
constant. It has its quirks, but JavaScript is growing on me.Rust
Rust
Side note: AoC really makes me appreciate Rust's ability to chain functions in the order that they're used, rather than having to nest them. To me, this is so clean:
As opposed to languages where this would need to be nested and written in the opposite order:
It turns out that the input to today's problem is also a valid Intcode program. If anyone wrote an Intcode VM for AOC 2019, it's worth running today's input through your VM.
Hah, I should've recognized that when I was examining my input. 2019 was the first year I got all the way through, and I loved all the Intcode problems.
That's a fun touch, and hopefully a hint of more VM-related problems to come.
Math tricks for days. I absolutely loved the problem description
Day 7 - Part A - Python
I can't really describe how I knew it, but I just sort of intuited that the median value was what I was looking for in this one. So I tried that and it worked.
Day 7 - Part B - Python
I'm sure there's a general equation for this, but I didn't want to deal with that nonsense, so I made a lookup table and then used that to do a very simple gradient decent starting at the median value. While true feels super dirty, but I think that's as close as python gets to do while.
Tips
I think this one is small enough that it can be brute forced? But there are definitely some shortcuts if you think about the underlying math
I'm pretty sure in both parts A and B there is only 1 local minima, which is also the global minima, which is also your target
I tried looking +-10 units around the mean of the numbers for the most efficient spot to converge on, but apparently that's not always it. So like Crespyl I threw my hands up in the air and just tried every possible value from the least to the greatest of the initial positions.
Had to look up some math junk to solve part 2 in a reasonable amount of time.
Both parts, Elixir
Racket
Part 1
Part 2
Notes
One thing I'm jealous of is in haskell all functions have 1 argument but the magic of syntax sugar lets you define and call them as if they had multiple arguments.
There is some syntax sugar in Racket to define your own curried procedures though which I used today.
The de-sugared version is
.. or fully de-sugared
A couple of drawbacks in Racket compared to e.g. haskell.
((fixed-cost 2) 7)
.curry/curryr
which I actually use quite often for AoC because I often want to map/fold using multi-argument procedures with only one argument varying.Google Sheets
I ended up bruting day 6 after nearly giving up this year. Today's was a little easier.
Part 1
Part 2
I was going to link another spreadsheet martyr I saw on reddit for day 6, but I see you bit the bullet!
I still think you're all deluded but good for you.
ha -- if I knew another way, I'd do that in a second. A buddy on IRC is solving with python then does it again in Haskall, then tries something else.
That being said, I'm pretty happy with day 7. So many of the spreadsheet folks are running thousands of formulas for this stuff. I had to cave for day 6, but unless the solve is pretty and fast, I don't want to bother.
/u/Mathgeek007 has made the leaderboard using Excel, which is insane.
Just brutforce
Python
Some people here rediscovered what I remembered learning at school as Gauss' Sum, but I can't find a corresponding wikipedia entry, and Gauss's Sum appears to be something else! Anyway, using (2n(n+1))/2 was a great way to calculate the cost of Part 2!
Rust on sourcehut
There's a classic story about Gauss adding up numbers in this way when he was a child, and he's briefly mentioned in the Wikipedia entry for triangular numbers. That's the name of the numbers described by this formula. Here's also the OEIS entry
I was a little worried I would need to rethink on part 2 but I think Nim compiling to C and then just using whatever C toolchain you want helped me out a lot! For those curious, on a debug build it takes about 6 seconds to do both parts, which drops to 2.6 with a release build. I also tried out the C++ backend to see the difference, and on release it was about the same speed, but on debug it slowed all the way down to 16 seconds? Not sure what is happening there.
Part 1
Part 2 Additions
Hello, I'm back! Took a couple days away, solving problems without posting.
This one was a good example of a problem where a little math knowledge can go a long way for optimization. Using absolute value instead of writing a loop to "step" each crab into place optimizes by a factor of O(n) -- a good lesson for beginning programmers :)
Part 1
Part 2
Day 07 (Rust)
Solution
Parsing
Parsing is simple today: the input is a single line with comma-separated numbers.
Part I
Fuel consumption
Let's first assume that we know the target. Then the fuel consumption calculation is trivial:
The target is the point that has the overall least distance to each submarine. If we assume that the positions are sorted, that point is the middle of the list (i.e. the median).
Edit: Of course that doesn't work in the general case... 🤦 but this was a nice opportunity to try out property-based testing with QuickCheck.
After reworking it a bit I came up with:
QuickCheck has so far been unable to disprove it, though it does assume that the list has at least two elements.
Edit 2: Or I could just have used the proper definition of a median instead of going from memory. 🤦
If anyone is still reading this, I apologize. Tomorrow I'll spend a while polishing everything before jumping into the thread with half-baked code...
Part II
Now we need to change how fuel consumption is calculated. Since more fuel is spent with every step, we have cost series of
1 + 2 + 3 ...
which of course has Gauss's famous sum formula:n * (n + 1) / 2
.Unfortunately, I can't really think of a better method than brute force for part 2, though I think you can bisect the list instead of iterating over every entry.
Main
Solutions in Go:
Part 1
CalcCost changes for Part 2
Can every day be a weekday? The last two days have been much easier than Saturday + Sunday (aside from yesterday's memory issues)
Anyway, pretty happy with how mine turned out, definitely could have re-written it to just be one function call, but I decided to leave it seperate
Parse Input
Part I
Part II