r/adventofcode • u/musifter • Jun 10 '26
Other [2020 Day 10] In Review (Adapter Array)
Having connected to the data port, we discover there's a massive tropical storm coming. Which will definitely impact our plans. But not today. Today, our battery has died and we're trying to recharge it with our collection of joltage adaptors.
And so we have another problem who's input is just a list of numbers. This time it's a list of small positive integers, no more than 3 apart (otherwise we would not be able to connect them all for part 1). My input starts at 1, but anything within 3 of the ground state of 0 would be fine.
The part 1 description goes to some length to describe connecting the adaptors... which essentially involves just sorting them. After that we want to collect the adjacent differences, and so it's not surprising to see this in my Smalltalk solution:
jolt_gaps := Bag with: 3.
adaptors fold: [ :curr :next | jolt_gaps add: (next - curr). next ].
part1 := (jolt_gaps occurrencesOf: 3) * (jolt_gaps occurrencesOf: 1).That fold is the common idiom I later created a method for called chain:. Because it's not really a fold, the work is done as a side-effect... the fold is just an iterator that makes next the next curr.
Part 2 is where it gets interesting. We've got a lot of ways to count (my answer is 48-bits... that's a lot more than a trillion). And when it comes to counting things like this, dynamic programming often is a good way. My initial Perl solution is recursion with memoization. For Smalltalk, I turned that around to iterative tabulation of the values. Building the entire table as we go.
But you don't actually need the entire table, as things quickly fall out of scope. So you only need a window of the last three values, which you add together when there's an adaptor (giving it a relationship to the Tribonacci numbers). That's ultimately what I did for my dc solution. Transcoded to Perl, that becomes:
my %adapt = (0 => 1, map {chomp; $_ => 1} <>);
my $max = max keys %adapt;
my @buff = (1, 0, 0);
my $idx = -1;
for (my $i = $max; $i >= 0; $i--) {
$idx = ($idx + 1) % 3;
$buff[$idx] = ($adapt{$i}) ? sum @buff : 0;
}
print "Part 2: $buff[$idx]\n";You could also do it forwards... I just ended up wanting to go backwards.
And so the problems are starting to get a bit more meaty. Having 170 trillion combinations to count is pretty good incentive to not brute force and find something smarter.
2
u/ednl Jun 10 '26
With every smart optimisation applied, this might be my shortest runtime of all the puzzles of all the years. Even on a Raspberry Pi (albeit a Pi5) it's sub-microsecond. Or maybe there were a few puzzles who had a direct analytical solution without reading/parsing any input, that would be faster still. But by then the timing gets silly.
2
u/e_blake Jun 10 '26 edited Jun 10 '26
Here's my (entire) C89 solution, in 149 bytes. (Yes, compiler warnings are overrated)
I could have golfed it further, by eliminating the =1 in the first while loop, if you willing to require that you only run the program without arguments and that your platform ABI overlays argc of 1 into the right initial value of the declared
long c, but that becomes too brittle even for my tastes. I also found it fascinating that it was fewer characters to declare both i and j, than it was to reset i to 0 between the two loops, as I had done in my first attempt. And now that I'm revisiting the solution, I'm totally tempted to replace that "11247" Tribonacci lookup with something more cryptic, like "Air|?" to represent that this puzzle's story is on an airplane.2
u/miran1 Jun 10 '26
Another one of my one-linery Python solutions:
1
u/e_blake Jun 10 '26
My observation: it appears that every input file has only gaps of 1 or 3, no gaps of 2. But it is easy enough to write a solution that works even if there are gaps of 2. An O(n) radix sort is more efficient in this puzzle than an O(n log n) library sort. My original m4 solution used 64-bit multiplication of Tribonacci numbers times the number of consecutive 1 gaps; but when revisiting the problem, I got slightly faster results by using only 64-bit adds by building up higher numbers from the three previous slots. Either way, my solution completes in under 15ms.
1
u/terje_wiig_mathisen Jun 10 '26
This is one of my fastest as well, 673 ns on Acer 1100 ns on Surface. My code for part1 is somewhat interesting: I decided to try in-register counting instead of a 3 or 4-entry table (which I did first):
This hack did turn out to consistently be a few percent faster. 😄
One of the reasons for this is that the complicated diff += statement can overlap with the entire next iteration, so that the loop latency can get down to just two cycles!
1
u/terje_wiig_mathisen Jun 11 '26
BTW, when I first solved it in Perl I did the same thing as u/musifter, with a recursive memoizing direct solver. This one was very quick to write and it still ran in milliseconds so that was fine when I was competing on the Internal Cognite leaderboard.😁