AoC 2021/8 - Treachery is contagious

TL;DR

On with Advent of Code puzzle 8 from 2021: the first betrayal of Raku 🙄

On december 8th I woke up early. I mean, it’s a bank holiday in Italy, and I was keen on getting some good result in Advent of Code, so why not?

Countdown ticking… 4… 3… 2… 1… open!

I start reading and I don’t know if it’s the early hours, my aging brain, or simply panic but I barely understand what’s written.

Well, there’s surely panic, because I do what most of us do under panic: revert to a safe spot. Which, for me, means coding in Perl. So yes, I confess this: my original solution to get the job done was in Perl.

In my defense, I was probably drawn on the dark side by The Treachery of Whales.

Jokes apart, Perl is amazing and regularly saves my day.

One thing I learned is to go fast up to the question. This was so true in this case: the first part of the puzzle is actually a very simple exercise of counting simple stuff, and I could have done it in much less than the 10:36 I actually scored. At least TIL 😅

The second part was much harder, and honestly I didn’t want my brittle knowledge of Raku to get in the way.

BUT.

This year I’m also determined into understanding more of Raku, so I eventually translated my solution into it. And, while on it, I tried to use some specific data structure that have to be imagined in Perl.

As an example, think using a hash to implement a set.

So here I come, repentant but not too much 😉:

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

sub MAIN ($filename = $?FILE.subst(/\.raku$/, '.tmp')) {
   my $inputs = get-inputs($filename);
   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.lines.map({
      my @chunks = .comb: / \w+ /;
      my @hints = @chunks.splice(0, 10);
      [@hints, @chunks];
   }).Array;
}

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

sub part1 ($inputs) {
   state %cfl = 2 => 1, 4 => 4, 3 => 7, 7 => 8;  # unambiguous lengths
   return $inputs
      .map({$_[1].Slip})            # get only outputs, individually
      .grep({%cfl{.chars}:exists})  # filter the right lengths only
      .elems;                       # count 'em
}

sub part2 ($inputs) {
   state %cfl = 2 => 1, 4 => 4, 3 => 7;  # unambiguous, used lengths
   my $sum = 0;
   for @$inputs -> ($hints, $outputs) {
      # first, let's collect a few statistics and detect 1, 4, and 7
      my %set;
      my $seen = BagHash.new;
      for @$hints -> $hint {
         $seen.add($_) for my @chars = $hint.comb: / \w /;
         %cfl{my $n = @chars.elems}:exists or next;
         %set{%cfl{$n}} = @chars.Set;
      }

      # next, build a mapping from "right" segment name to "jumbled"
      # mapping for "a" is by difference from "7" and "1"
      my %sof = a => (%set<7> (-) %set<1>).keys[0];

      # some frequencies are known
      state %known = 4 => 'e', 6 => 'b', 9 => 'f';

      # iterate over the %seen statistic to determine the mapping
      for $seen.kv -> $k, $n {
         if (%known{$n}:exists) { %sof{%known{$n}} = $k }
         elsif ($n == 8) { %sof<c> = $k if $k ne %sof<a> }
         elsif ($n == 7) {
            my $right = (%set<4> (-) set($k)).elems == 3 ?? 'd' !! 'g';
            %sof{$right} = $k;
         }
      }

      # with the %sof mapping we can assign a value to each sequence
      #my %nfor = (0 .. 9).map: { assemble(%sof, $_) => $_ };
      my %nfor = assemble(%sof);

      # we can determine the output digits at last
      $sum += $outputs.map(
         {
            my $key = .comb(/\w/).sort({$^a cmp $^b}).join('');
            %nfor{$key};
         }
      ).join('');
   }
   return $sum;
}

sub assemble (%sof) {
   state @segments-for = [
      [< a b c   e f g >],
      [<     c     f   >],
      [< a   c d e   g >],
      [< a   c d   f g >],
      [<   b c d   f   >],
      [< a b   d   f g >],
      [< a b   d e f g >],
      [< a   c     f   >],
      [< a b c d e f g >],
      [< a b c d   f g >],
   ];
   return (0 .. 9).map: -> $n {
      %sof{@segments-for[$n].Slip}.sort({ $^a cmp $^b }).join('') => $n
   };
}

The margins of this blog are too narrow to put a full explanation of what’s going on… I hope the comments are sufficient!

Stay safe everyone!


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