TL;DR

Advent of Code 2021 started!

So! It’s that time of the year again, when Eric Wastl starts unveiling 50 (49?) carefully crafted puzzles attached to a Christmas-related story.

For the story, I think Reddit user aang333 put it in the best way:

Why AoC

Yes, the red thread is so funny!

The first puzzles are usually more on the warm-up side, and this year was no exception. So I decided to try and address them in Raku, at least until I’ll have to decide between my Perl “muscle memory” and the ambition to learn more Raku.

Even day 2 put my patience to some stress test, anyway, because data structures and listy stuff handling is much more precise in Raku and it’s difficult for me to figure out the exact expression to reach the goal I have in mind.

First puzzle

I was ashamed by my solution after reading stuff from other people. For two reasons.

First, it’s entirely clear that my go-to solution is to implement stuff the Perl way, or so. I mean, there’s nothing to be ashamed - Perl is amazing and Raku feels so good with some Perl background - but I feel that I’m somehow missing on a lot of new cool stuff by not exploring metaoperators, rotor and I don’t know what else. Sort of going to a restaurant and always take the few dishes that we know are good, if you know what I mean.

Second, and foremost, the solution to the second part could be much simpler than I coded it. It asks to compare two consecutive values in a sequence of sums calculated on a sliding window of the original data, which I did literally. Except that this was way unnecessary, because comparing two consecutive sums means comparing:

\[x_k + \underbrace{x_{k+1} + x_{k+2}} < \underbrace{x_{k+1} + x_{k+2}} + x_{k+3}\]

You see? We can get rid of stuff from both sides of the inequality, and compare what remains, i.e.:

\[x_k < x_{k+3}\]

There you go, no need to sum any window etc, just compare an element with the third successor down the road.

What a shame 🙄

Anyway, for the records, here’s my code:

#!/usr/bin/env raku
use v6;

sub MAIN ($filename = Nil) {
   my $inputs = get-inputs($filename // $?FILE.subst(/\.raku$/, '.tmp'));
   my ($part1, $part2) = solve($inputs);

   my $highlight = "\e[1;97;45m";
   my $reset     = "\e[0m";
   put "part1 $highlight$part1$reset";
   put "part2 $highlight$part2$reset";
}

sub get-inputs ($filename) {
   $filename.IO.basename.IO.lines.Array;
} ## end sub get_inputs ($filename = undef)

sub solve ($inputs) {
   return (part1($inputs), part2($inputs));
}

sub count-increases (@inputs) {
   my $count = (1 .. @inputs.end)
      .map({@inputs[$_] > @inputs[$_ - 1] ?? 1 !! 0 })
      .sum;
}

sub part1 ($inputs) { return count-increases($inputs) }

sub part2 ($inputs) {
   return count-increases(
      (1 ..^ $inputs.end).map({$inputs[($_-1)..($_+1)].sum})
   );
}

There are a lot of solutions in the solution megathread; today’s prize goes to this:

#!/bin/env raku

sub MAIN(Str:D $f where *.IO.e = 'input.txt') {
    my @n = $f.IO.words;
    put 'part 1: ', [+] @n Z< @n[1..*];
    put 'part 2: ', [+] @n Z< @n[3..*];
}

It was, by the way, the solution that got me thinking about doing my maths correctly! Apart from the mathematical epiphany, anyway, it’s jam-packed of treasures, just there in plain sight:

  • a test for existence of the input filename just there in the signature?!?
  • zipping the comparison operator?!?
  • summing without .sum?!?

I’m multiply humbled.

Second puzzle

The second puzzle is not particularly difficult, and at this time of writing these notes I still have to look at other solutions.

I mean, there’s a lot to learn from there, but I feel my ego is still recovering from yesterday. Additionally… while coding my solution there was a small voice in the back of my head telling me that surely there’s some more compact and Raku-ish way to do this!.

Yes, I hear voices in the back of my head.

Only when coding solutions to puzzles anyway.

Which, admittedly, happens quite often.

Whatever.

So here’s my solution - boring maybe, scarcely idiomatic maybe, but still working Raku code!

#!/usr/bin/env raku
use v6;

sub MAIN ($filename = Nil) {
   my $inputs = get-inputs($filename // $?FILE.subst(/\.raku$/, '.tmp'));
   my ($part1, $part2) = solve($inputs);

   my $highlight = "\e[1;97;45m";
   my $reset     = "\e[0m";
   put "part1 $highlight$part1$reset";
   put "part2 $highlight$part2$reset";
}

sub get-inputs ($filename) {
   $filename.IO.basename.IO.lines.map: { .split: /\s+/ };
} ## end sub get_inputs ($filename = undef)

subset Depth of Int where * >= 0;

sub solve ($inputs) {
   return (part1($inputs), part2($inputs));
}

sub part1 ($inputs) {
   my (Depth $depth, $hp) = 0, 0;
   for @$inputs -> $command {
      my ($direction, $amount) = @$command;
      given $direction {
         when 'forward' { $hp += $amount    }
         when 'up'      { $depth -= $amount }
         when 'down'    { $depth += $amount }
         default        { die 'WTF?!?'      }
      }
   }
   return $hp * $depth;
}

sub part2 ($inputs) {
   my (Depth $depth, $hp, $aim) = 0, 0, 0;
   for @$inputs -> $command {
      my ($direction, $amount) = @$command;
      given $direction {
         when 'forward' { $hp += $amount; $depth += $aim * $amount }
         when 'up'      { $aim -= $amount }
         when 'down'    { $aim += $amount }
         default        { die 'WTF?!?'    }
      }
   }
   return $hp * $depth;
}

And, of course, I had to go looking in the day 2 solutions megathread! I know there are a few Raku solutions, but I was fascinated by this (crafted by user Orac1e):

my @dirs = 'input'.IO.words;

put [×] [Z+] @dirs.map: -> $_, $x {
    when 'up'      { (0,-$x) }
    when 'down'    { (0, $x) }
    when 'forward' { ($x, 0) }
}

put [×] [Z+] @dirs.map: -> $_, $x {
    state $a = 0;
    when 'up'      { $a -= $x; next }
    when 'down'    { $a += $x; next }
    when 'forward' { ($x, $a × $x)  }
}

I know one day I’ll write stuff like this and think to that time when I wasn’t able to do it…

Or, much more probably, this will never happen but still I’m able to have an intuition about what’s going on, which is perfectly fine at this stage.

So why splitting into words? Well, map is more general in Raku, so you can get multiple elements at a time and this is exactly what’s going on here.

Using the topic as the first argument variable of the code block is the evil genius touch here. It allows sparing a given and go straight to use when checks.

After resolving what comes after @dirs, the metaoperators kick in from right to left. So Orac1e first processes all pairs to sum them dimension-wise, then does the final multiplication. Wow.

And now, one last candy from seaker, here:

my ($x, $aim, $y) X= 0;

This!

Well, enough for today. Give Advent of Code a try, but don’t forget to stay safe!