AoC 2022/2 - Rock Paper Scissors cheat guide

TL;DR

On with Advent of Code puzzle 2 from 2022: elves are so predictable!

The second day seems to follow the unwritten rules of getting slightly more difficult than yesterday, but not too much.

Which, of course, already got me providing the wrong solution for both part 1 and part 2 in the first place.

Inputs are pairs of letters, each representing a single draw in the game. It seems that elves are quite predictable at playing the game, so much so that they collected an extensive cheat guide that is able to predict exactly what the opponent is going to draw. Every. Single. Game.

I wonder how the game stuck with them, honestly. If all elves play like that, they must have endured endless draws, right?

Anyway.

Reading the inputs this time was much easier: each line contains two characters, so I decided to comb on sequences of non-space characters:

sub get-inputs ($filename) {
   $filename.IO.lines».comb(/\S+/).Array
}

It seems that .comb gives me back tuples that can be reused for both parts, so I don’t have to do anything fancy for turning each of them into something more durable. Go figure.

My initial solution was… messy. It was something like this:

sub part1 ($inputs) {
   my %score-for = <X 1 Y 2 Z 3 A 1 B 2 C 3>;
   my %dscore-for = <0 3 1 0 2 6>;
   my $sum = 0;
   for @$inputs -> $tuple {
      my ($elf, $me) = $tuple.map({ %score-for{$_} });
      my $outcome = (($elf - $me) % 3);
      $sum += $me + %dscore-for{$outcome};
   }
   return $sum;
}

I’m not sure that works, to be honest. My initial solution had the .comb directly inside part 1, anyway this gives the gist of the solution: using hashes to get workable values.

We can get rid of the mapping by just doing maths on the .ord value for each input character, then we can observe that we don’t need no stinkin’ for loop when we have .map. This leads us to this:

sub part1 ($inputs) {
   return $inputs
      .map({[$_[0].ord - 'A'.ord, $_[1].ord - 'X'.ord]})
      .map({
           1 + $_[1]
         + 3 * ((1 + $_[1] - $_[0]) % 3)
      })
      .sum;
}

It’s possible to coalesce the two .map into one, but I think it’s more readable in this way. The first one turns characters into values (this time in range 0..2), while the second one does the actual score calculation according to the rules in part 1.

The first .map might be even moved in the get-inputs function, but this would mean anticipating what’s needed for part2 and I feel that it’s sort of cheating. I know, we’re talking ex-post here.

The second part just asks to apply a different algorithm. It’s possible to reuse the whole of part 1 scaffolding and just change the .map where the calculation is done:

sub part2 ($inputs) {
   return $inputs
      .map({[$_[0].ord - 'A'.ord, $_[1].ord - 'X'.ord]})
      .map({
           3 * $_[1]
         + 1 + ($_[0] + $_[1] - 1) % 3;
      })
      .sum;
}

In hindsight, I think that nothing beats a lookup table in this case, as there are only 9 different cases. This would allow us to work directly on each line, without even parsing it:

my %score-for = 'A X' => 4, 'A Y' => 8, 'A Z' => 3 ...

But we all know… hindsight is 20/20!

I guess this is it for today, stay safe!


Comments? Octodon, Twitter, GitHub, Reddit, or drop me a line!