2020 AoC Day 16 – Ticket Translation

This is a solution to Advent of Code 2020 day 16, written in Raku.

https://adventofcode.com/2020/day/16

Part One

Consider the validity of the nearby tickets you scanned. What is your ticket scanning error rate?

Raku

Raku when blocks are good for ad-hoc parsing where lines will be matched with different regular expressions. A series of when blocks acts like a switch statement with super powers.

The solution for part one just requires us to gather all the ranges from the rules entries at the beginning of the input and then find all the values that are not an element of any range.

  my @lines = '16-input.txt'.IO.lines;

  my @ranges;
  my @invalid;
  my Bool $ignore = True;

  for @lines {
      when /(\d+) '-' (\d+) ' or ' (\d+) '-' (\d+)/ {
          @ranges.push(+$0..+$1);
          @ranges.push(+$2..+$3);
      }
      when /'your ticket'/ {
      }
      when /'nearby tickets'/ {
          $ignore = False;
      }
      when /(\d+)+ % ','/ {
          next if $ignore;
          my @values = $0.map(*.Int);

          my @nonmembers = @values.grep(-> $v { [&&] @ranges.map( -> $r { $v !(elem) $r } ) } );
          @invalid.append(@nonmembers);
      }
  }

  say "Part One";
  say [+] @invalid;
Part One
32835

Part Two

Once you work out which field is which, look for the six fields on your ticket that start with the word departure. What do you get if you multiply those six values together?

Raku

Part two builds on part one so now we need to capture the complete rules. It is necessary to find all columns for which each rule is valid and then solve the mapping by sorting them by fewest matches first. The first rule only has one valid column and once it is mapped, the next rule only has one valid column left, and so on.

  my @lines = '16-input.txt'.IO.lines;

  my @all-ranges;
  my @invalid;
  my Bool $mine = True;

  my %rules;
  my %rule-columns;
  my @columns;

  my @my-ticket;

  for @lines {
      when /(<[\w\s]>+) ': ' (\d+) '-' (\d+) ' or ' (\d+) '-' (\d+)/ {
          my $rule-name = ~$0;
          my @ranges = +$1..+$2, +$3..+$4;
          %rules{$rule-name} = @ranges;
          @all-ranges.append(@ranges);
      }
      when /'your ticket'/ {
      }
      when /'nearby tickets'/ {
          $mine = False;
      }
      when /(\d+)+ % ','/ {
          my @values = $0.map(*.Int);

          if $mine { @my-ticket = @values; next; }

          my @nonmembers = @values.grep(-> $v { [&&] @all-ranges.map( -> $r { $v !(elem) $r } ) } );
          @invalid.append(@nonmembers);

          next if +@nonmembers;

          @columns[$_].push(@values[$_]) for ^+@values;
      }
  }

  say "Part One";
  say [+] @invalid;

  say "Part Two";

  my %valid-columns = do for %rules.kv -> $k, $v {
      $k => (do for ^+@columns -> $i {
                    $i if ([+] @columns[$i].map(
                               -> $n {
                                   [&&] $v.map(
                                       -> $r {
                                           $n !(elem) $r
                                       }
                                   )}
                               )) == 0
      })
  }

  my %column-mappings;
  my SetHash $used .= new;

  for %valid-columns.sort(+*.value) -> $pair {
      my $remaining = $pair.value.Set (-) $used;
      my $value = $remaining.keys[0];
      $used.set($value);

      %column-mappings{$pair.key} = $value;
  }

  my @wanted = %column-mappings.keys.grep({.starts-with('departure')});
  say [*] @my-ticket[%column-mappings{@wanted}];
Part One
32835
Part Two
514662805187
raku