Advent Of Code 2021, Day 2: Dive!

by Abigail

Challenge

We're given an input file with instructions of moving a submarine. The file looks like this:

forward 5
down 5
forward 8
up 3
down 8
forward 2

Part One

In Part One, we keep track of two variables: the horizontal position (the direction the submarine has travelled in the forward direction), and the depth (how deep the submarine has dived):

The instructions are to be interpreted as:

To get the answer, we have to process all the instructions from the input, and then multiply the final horizontal position with the final depth.

For the example file above, the answer would be 150.

Part Two

In Part Two, we introduce an additional parameter to track: aim.

Now, the instructions are to be interpreted as:

Again, to get the answer, we process all the instructions, then multiply the final horizontal position, and final depth.

For the example file above, the answer would be 900.

Solutions


For both parts, the final horizontal position will be the same.


The value of the depth in part one, is equal to the value of aim in part two.

Perl

For our Perl solution, we use three variable to keep track:

First, we initialize the variables:

my $forward = 0;
my $depth1  = 0;
my $depth2  = 0;

Then we loop over the input, updating the appropriate variables depending on the command:

while (<>) {
    my ($cmd, $amount) = split;
    if ($cmd =~ /^f/) {$forward += $amount; $depth2 += $amount * $depth1}
    if ($cmd =~ /^d/) {$depth1  += $amount}
    if ($cmd =~ /^u/) {$depth1  -= $amount}
}

Then we can print the solutions:

say "Solution 1: ", $forward * $depth1;
say "Solution 2: ", $forward * $depth2;

Find the full program on GitHub.

Bash

Remarkably, our Bash solution is very similar to the Perl solution. We don't need to bother initializing our variables, so we go straight to the loop:

while read cmd amount
do  case $cmd in
        f*) ((forward += amount))
            ((depth2  += amount * depth1)) ;;     
        d*) ((depth1  += amount))          ;;     
        u*) ((depth1  -= amount))          ;;     
    esac
done

Instead of three if statements, we have a single case statement, but the effect is the same. In either case, exactly one of the options will be executed.

After the loop, we print the results:

echo Solution 1: $((forward * depth1))
echo Solution 2: $((forward * depth2))

Find the full program on GitHub.

Evil Perl

Inspired by a solution from musifter, we can write the main loop also as:

while (<>) {
    my ($cmd, $amount) = split;
    $forward += !('forward' cmp $cmd) * $amount;
    $depth1  +=  ('forward' cmp $cmd) * $amount;
    $depth2  += !('forward' cmp $cmd) * $amount * $depth1;
}

Note the absense of an if statement in the loop.

How does this work? They key lies in the value of 'forward' cmp $cmd. cmp compares two strings, and returns -1, 0, or 1 depending if the first string is (string-wise) less, equal or greater than the second.

forward lies in between down and up. So, we get the following table:

$cmd ('forward' cmp $cmd) !('forward' cmp $cmd)
"down" 1 0
"forward" 0 1
"up" -1 0

The result is that if the command is forward, we increment $forward and $depth2, but we leave $depth1 unmodified. If the command is down, we increment $depth1 by $amount, leaving $forward and $depth2 unmodified. And if the command is up, we decrement $depth1 by $amount (because ('forward' cmp $cmd) is -1), leaving $forward and $depth2 unmodified.

This is exactly the same as we did in the regular Perl solution.

Find the full program on GitHub.

Very Evil Perl

We also have a very evil Perl solution (works only for Part One):

perl -pale'${$F[0]}+=$F[1]}{$_=$forward*($down-$up)' input

This abuses symbolic references, and exploits the ways how perl (the program, not the language), modifies the source of the program it's about to compile when seeing a -p or -n command line switch, by using the Eskimo greeting.


Please leave any comments as a GitHub issue.