The 2022 advent of code had a nice variety of problems which provided an excuse to use some of the unique features of the Raku programming language. Below are a number of my Raku solutions, with some explanations. This collection of problems had some common themes, so I'm putting them together here. I might write up another collection in a future post.
Note that to see the problems and the inputs, you'll need to follow the links below. I am only including the solutions, and commentary about how they work. Some of what follows might not make sense if you haven't read the problems, or perhaps tried to solve them yourself.
Day 1: Calorie Counting
In this problem we want to find the sums of a sequence of lists, then the maximum value, and then the top three values.
      To read the input, we use $*ARGFILES which refers to files
      given on the command line. i.e.  To run this program, save it
      as day-01.raku, save the input as input.txt, and then
      type raku day-01.raku input.txt.
    
      The map is a method call, and * creates an anonymous
      function.  Method calls can use either colons or parentheses.
      There are other ways to make anonymous functions -- for instance,
      each of these lines does exactly the same thing:
    
  .map: *.lines
  .map( *.lines )
  .map( { .lines } )
  .map( { $_.lines } )
  .map( { $^l.lines } )
  .map(-> $l { $l.lines } )
    
    The lines are coerced to numeric values when they are used by sum.
    my $input = $*ARGFILES.slurp;
my $elves = $input.split("\n\n");
my @totals = $elves.map: *.lines.sum;
# part 1
put @totals.max;
# part 2
put @totals.sort.tail(3).sum;
    
  
Day 3: Rucksack Reorganization
So we have a list of strings of letters, and we want to find 1) the letters that the first and second half of each list has in common, and 2) the letters that groups of three of the strings have in common. Then we want to sum values corresponding to those letters.
      We make %vals to map the letters to their values.  Note that Z stands
      for "zip" -- it combines two lists.  It takes an operator as a parameter,
      so by giving it the pair construction operator, =>, we construct a list
      of pairs.  This becomes a hash when we store it in %vals. Z is called
      a "metaoperator" because it is an operator that uses another operator.
    
      Another metaoperator is [] -- also known as reduce.  This applies
      a binary operator to consecutive elements of a list.
    
      For instance, [+] (a,b,c,d) is a + b + c + d.
    
      When we use set intersection, ∩
      with the meta reduction operator, we are taking
      the intersection of a list of lists.  So for part one, [∩] is the common elements of the
      first and second half of the list, and for part two, [∩] is the intersection
      of three lists.
    
Also, comb divides a string into characters, and rotor takes elements of a list several at a time.
      By the way, ∩ can be written as (&): all of Raku's unicode operators
      have ASCII equivalents.
    
    my $in = $*ARGFILES.slurp;
my %vals = ('a'..'z','A'..'Z').flat Z=> 1..52;
# part 1
say sum $in.lines.map: -> $backpack {
    %vals{ [∩] $backpack.comb[0 .. */2 - 1, */2 .. * ] }
}
# part 2
say sum $in.lines.rotor(3).map: -> $group {
  %vals{ [∩] $group.map(*.comb) }
}
    
  
Day 6: Tuning Trouble
We want to find the first time that four consecutive characters are all different. Fourteen for part two.
      We comb the string into a sequence of letters, and convert the sequence into a list.
    
      Then -- remember rotor above?   -- well, it can take
      a Pair as an argument to return a list of lists with overlaps (that is 1,2,3, 2,3,4 etc).
      That's handy for this problem!
    
      Also first searches for an element of a list.
      And when we give it the adverb :k, we get the index, not the element.  This is useful for a few
      problems this year.
    
    my $in = $*ARGFILES.slurp.comb.list;
# part 1
say 4 + $in.rotor(4 => -3).first: :k, *.unique.elems == 4;
# part 2
say 14 + $in.rotor(14 => -13).first: :k, *.unique.elems==14
    
  
Day 7: No Space Left on Device
      We are examining the output of various cd and ls commands to compute
      the sizes of directories on a filesystem, and find the directories using the most space (part 1)
      and the minimal set of directories to remove to free up some space (part 2).
    
There are a few interesting things here:
      First, we can make regexes which are building blocks for the matches in the when
      statements.  The names of the regexes can then be used in the corresponding statements.
      That is, we make a regex named dir, and then after matching it using <dir>, we can
      refer to $<dir> to get the match.  $<dir> is exactly equivalent to $/<dir> -- the
      special variables $/ contains the results of the match, and it holds the named captures,
      like dir, size and filename.  One of the nice features of Raku is that it made
      regexes composable -- and this is a small example of using that feature.
    
      Also, the :s in the regexes in the when clauses means that whitespace is significant:
      e.g. the space between cd and <dir> will match whitespace in the input.
    
      Second, remember the metareduction operator [] above?  Well there is also a version with
      a \ called the "triangular" metareduction operator.  The
      difference is that this one returns the intermediate results:
    
[+] (a,b,c,d) is a + b + c + d
[\+] (a,b,c,d) is (a), (a+b), (a+b+c), (a+b+c+d)
    
    
      Instead of +, we can use concatenation, ~, to get all the parent directories
      of a particular subdirectory.  ( /a, /a/b, /a/b/c...)
    
      Also »+=» is another hyper operator -- this applies += pairwise between two lists.  i.e. perform
      vector addition.  (It can also be written as >>+=>>).  The list repetition operator, xx, just
      repeats an element to create a list.
    
    my $in = $*ARGFILES.slurp;
my @cwd = '/';
my %totals;
my regex dir { <[a..z]>+ }
my regex size { <[0..9]>+ }
my regex filename { \w+ }
for $in.lines {
  when /:s '$' cd '/'    / { @cwd = ('/')         }
  when /:s '$' cd <dir>  / { @cwd.push("$<dir>/") }
  when /:s '$' cd '..'   / { @cwd.pop             }
  when /:s <size> <filename>/ {
    %totals{ [\~] @cwd } »+=» $<size> xx *
  }
}
# part 1
say %totals.values.grep( * <= 100_000).sum;
# part 2
my $total-space = 70_000_000;
my $unused = $total-space - %totals{ '/' };
my $need = 30_000_000;
my %choices = %totals.grep: { $unused + .value > $need }
say %choices.values.min;
    
  
Day 8: Treetop Tree House
We have a grid of numbers and we want to: (1) find all trees which are visible from outside the grid, and (2) find the most "scenic" spot.
      First of all, X is the cross-product meta-operator.  Like Z (above)
      it takes two lists, but instead of corresponding elements it takes all
      possible pairings.  That is, 1,2 X~ 3,4 is 13,14,23,24.
    
      And by the way, you can make your own infix operators.  So, 🌳 is
      an operator here that finds the index of the first element of a list
      which is larger than a given value.
    
      I can then use X to just apply this in all four directions, and take
      the product (using [*] to reduce it), and then keep track of the
      maximum (for part 2).
    
      For part one -- the all and
      any are junctions.
      Junctions are a nice construct since they turn individual values into
      a superposition of values -- a simpler example would be 1 == any(1,2,3)
      which compares 1 to three other values.  These autothread -- meaning
      the expression might be evaluated in parallel in different threads at
      the same time.
    
      Another kind of nice thing here is being able to take a slice in any
      direction of a two dimensional array, like this: @forest[row^..N;col].
    
      The range operator, .., constructs a Range with two endpoints, and the variants ^.. and ..^
      can be used to excude the left or right endpoint.  Also ^5 is a range too --
      the numbers from 0 to 4.
    
      Oh also, the expression $in.lines».comb applies .comb to each line.
      The operator  » is a "hyperoperator" -- it is equivalent to map,
      except that it also might autothread.
    
      You might also notice that some of these variables are declared with \ -- this
      allows them to be used without sigils, e.g. N, height, row, and col.
      In cases where it's obvious what kind of a variable something is, it can be
      a little cleaner to leave the sigil out, instead of having a $ in front of it
      each time.
    
    my $in = $*ARGFILES.slurp;
my @forest = $in.lines».comb;
my \N = @forest.elems - 1;
sub infix:<🌳>(@trees,\height) {
  return $_ + 1 with @trees.first: :k, * >= height;
  @trees.elems
}
my ($visible, $scenic-score) = (0,-Inf);
for 0..N X 0..N -> (\row, \col) {
  my \height = @forest[row;col];
  $visible++ if [
    @forest[row;^col].all, @forest[row;col^..N].all,
    @forest[^row;col].all, @forest[row^..N;col].all
  ].any < height;
  $scenic-score max= [*] [
    @forest[row;^col].reverse, @forest[row;col^..N],
    @forest[^row;col].reverse, @forest[row^..N;col]
  ] X🌳 height
}
say $visible;     # part 1
say $scenic-score # part 2
    
  
Day 13: Distress Signal
For day 13, we are asked to create a new comparison operator. The operator should behave differently depending on whether the operands are integers or lists.
      First, to convert a string of brackets and commas into a nested list, we
      use from-json from JSON::Fast.  (Install it with zef install JSON::Fast )
    
      We use the hyperoperator, », twice on the input -- once to turn all the pairs
      into two strings, and then once to turn each of the two strings into json.  Again,
      this operator works just like map.
    
      This problem is an even better case for making a custom operator -- since the
      problem basically describes a new operator.   This time we make one
      called ◆, and we can make use of multiple-dispatch.
    
      When we define a function with multi,
      it is a multiple-dispatch candidate; i.e. the calls in the arguments are mapped
      to the version with a matching signature.  Are we comparing two
      integers?  Are we comparing a list to an integer?  Are we
      comparing two lists?  The candidates correspond to the possibilities in the
      problem description.
    
      We can use this our custom operator recursively, we can use it with Z, and we
      can fall back to the well know space-ship operator <=> which just compares
      two integers.
    
      Finally, when we are flattening our list of pairs, we use the syntax @lines[*;*] --
      this turns a list of lists into a list, then we can append the sentinel values
      to this before finding their indices (using first again).
    
    use JSON::Fast;
my $in = $*ARGFILES.slurp;
my @lines = $in.split("\n\n")».lines».map: &from-json;
# ◆: compare packets
multi infix:<◆>(Int $a, Int $b) { $a <=> $b   }
multi infix:<◆>(@a, Int $b)     { @a ◆ [ $b ] }
multi infix:<◆>(Int $a, @b)     { [ $a ] ◆ @b }
multi infix:<◆>(@a, @b) {
  (@a Z◆ @b).first(* != Same) or (@a.elems <=> @b.elems)
}
# part 1
my @less = @lines.grep: :k, { .[0] ◆ .[1] == Less }
say sum @less »+» 1;
# part 2
my @flat = |@lines[*;*], [[2],], [[6],];
my @sorted = @flat.sort: &infix:<◆>;
my $first  = 1 + @sorted.first: :k, * eqv [[2],];
my $second = 1 + @sorted.first: :k, * eqv [[6],];
say $first * $second;
    
  
This post demonstrated a few of the unique features of the Raku programming language: hyperoperators, metaoperators, custom operators, composable regexes, junctions, and a variety of constructs for making anonymous functions.
Solutions to the other advent of code problems can be found here. A future blog post may have some more explantations about each of those.