ETOOBUSY 🚀 minimal blogging for the impatient
AoC 2016/15 - Chinese Reminder Theorem - again!
TL;DR
The Advent of Code puzzle 15 from 2016 has more Chinese Remainder Theorem!
As you might have noticed, I’ve been taking a look at the 2016 edition of the puzzles in Advent of Code.
No, this will not be another series!
It so happens that puzzle 15 has a long and involved description about capsules, discs, alignments, exact timings… I had to read it twice and put my brain in full imaginative mode.
Anyway, as I read through it, I started suspecting that it had to do with the Chinese Remainder Theorem. Which, indeed, it does.
First thought was: again?!? Then I had that Back to the Future moment when I realized that this puzzle came before the ones I discussed last December 🙄
Second thought was: I don’t want to understand this problem, I want to COOOODE!.
He who its first…
… hits twice, right?
So I went ahead, took the relevant functions from cglib’s Numbers.pm, put some parsing, a bit of this, a bit of that… and ended up with the following:
#!/usr/bin/env perl
use 5.024;
use warnings;
use English qw< -no_match_vars >;
use autodie;
use experimental qw< postderef signatures >;
no warnings qw< experimental::postderef experimental::signatures >;
use File::Basename qw< basename >;
use Data::Dumper; $Data::Dumper::Indent = 1;
use Storable 'dclone';
$|++;
my @stuff;
my $filename = shift || basename(__FILE__) =~ s{\.pl\z}{.tmp}rmxs;
open my $fh, '<', $filename;
while (<$fh>) {
my ($delay, $n, $position) = m{
\A Disc \s+ \#(\d+) \s+
has \s+ (\d+) \s+ positions .*?
at \s+ position \s+ (\d+)
}mxs or die $_;
push @stuff, $n, ($delay + $position) % $n;
}
close $fh;
say((chinese_remainder_theorem(@stuff))[1]);
# chinese_remainder_theorem and egcd below... nothing new
I have to admit that I was a bit unsure about the ($delay +
$position) % $n
- it was somehow a shot in the dark.
Anyway, I run it over the example input and presto! - it worked! Right off the bat!
$ cat 15.tmp
Disc #1 has 5 positions; at time=0, it is at position 4.
Disc #2 has 2 positions; at time=0, it is at position 1.
$ perl 15-1.pl 15.tmp
5
OK, on with my puzzle input then:
$ cat 15.input
Disc #1 has 13 positions; at time=0, it is at position 1.
Disc #2 has 19 positions; at time=0, it is at position 10.
Disc #3 has 3 positions; at time=0, it is at position 2.
Disc #4 has 7 positions; at time=0, it is at position 1.
Disc #5 has 5 positions; at time=0, it is at position 3.
Disc #6 has 17 positions; at time=0, it is at position 5.
$ perl 15-1.pl 15.input
64118
Only that… NO, it does not work!!!
I hit first… but I hit wrong!
Back to the paper
This must be my humbling year, because I’m reminded so many times of how many ways I have to fail!
Well, I meant learn 👨🎓
Let’s see… if we start at time $T$, the first disk is reached after a delay of $1$ at time $T + 1$, and if its starting position (at $t = 0$) is $P_{1, 0}$ then its position at $T + 1$ will be $T + 1 + p_1 \pmod {n_1}$. We have similar relations for the other discs:
\[P_{1, T + 1} \equiv T + 1 + P_{1, 0} \pmod {n_1} \\ P_{2, T + 2} \equiv T + 2 + P_{2, 0} \pmod {n_2} \\ ... \\ P_{i, T + i} \equiv T + i + P_{i, 0} \pmod {n_i}\]If we really need that capsule, each of the left-hand sides MUST be $0$, which brings us to:
\[T \equiv -1 - P_{1, 0} \pmod {n_1} \\ T \equiv -2 - P_{2, 0} \pmod {n_2} \\ ... \\ T \equiv -i - P_{i, 0} \pmod {n_i}\]This can also be rewritten as:
\[r_1 = T \pmod {n_1} = n_1 - (1 + P_{1, 0} \pmod {n_1}) \\ r_2 = T \pmod {n_2} = n_2 - (2 + P_{2, 0} \pmod {n_2}) \\ ... \\ r_i = T \pmod {n_i} = n_i - (i + P_{i, 0} \pmod {n_i})\]I knew it!
It’s also funny that it worked for the example input: simply put, $1 \equiv -1 \pmod 2$, so the sign flip didn’t matter!
So the right code is actually this… a small change for a program, but a big step ahead for a puzzle solver!
#!/usr/bin/env perl
use 5.024;
use warnings;
use English qw< -no_match_vars >;
use autodie;
use experimental qw< postderef signatures >;
no warnings qw< experimental::postderef experimental::signatures >;
use File::Basename qw< basename >;
use Data::Dumper; $Data::Dumper::Indent = 1;
use Storable 'dclone';
$|++;
my @stuff;
my $filename = shift || basename(__FILE__) =~ s{\.pl\z}{.tmp}rmxs;
open my $fh, '<', $filename;
while (<$fh>) {
my ($delay, $n, $position) = m{
\A Disc \s+ \#(\d+) \s+
has \s+ (\d+) \s+ positions .*?
at \s+ position \s+ (\d+)
}mxs or die $_;
push @stuff, $n, $n - ($delay + $position) % $n;
}
close $fh;
say((chinese_remainder_theorem(@stuff))[1]);
# ...
Let’s run it…
$ perl 15.pl 15.input
376777
Yay, this is correct now!
A final thought
I work in the telecommunications industry and most of my… coding occasions come in relation to relatively small integrations, so I definitely have a biased view.
This said… I wonder if the Chineses discovered this theorem just for the fun of puzzle builders and solvers in the 21st century!