2020 AoC Day 21 – Allergen Assessment

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


Part One

Determine which ingredients cannot possibly contain any of the allergens in your list. How many times do any of those ingredients appear?

The first step in solving this problem is to parse the input and build a Bag of all ingredient occurrences and a mapping of allergen to possible ingredients. The parsing regex uses the <pattern> % <separator> modified quantifier to parse a space separated list of ingredients and a comma separated list of allergens.

The solution to part one is then the sum of all ingredients that don't appear in allergen lists. This is calculated as the sum of remaining elements in the $ingredients Bag after all the allergen ingredients have been subtracted.

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

  my $ingredients = BagHash.new;
  my %mappings;
  for @input {
      when /^ (\w+)+ % ' ' ' (contains ' (\w+)+ % ', ' ')' / {
          my @ingredients = $0>>.Str;
          $1>>.Str.map(-> $k { %mappings{$k}.push(@ingredients.Set) } ).sink
      default {
          fail "Got {$_}";

  my %possible-allergens = %mappings.kv.map(
      -> $allergen, @sets { $allergen => [(&)] @sets }
  my $inert-ingredients = $ingredients.Set (-) ([(+)] %possible-allergens.values);

  say "Part One";
  say [+] $ingredients{$inert-ingredients.keys};
Part One

Part Two

Arrange the ingredients alphabetically by their allergen and separate them by commas to produce your canonical dangerous ingredient list. (There should not be any spaces in your canonical dangerous ingredient list.) In the above example, this would be mxmxvkd,sqjhc,fvjkl.

Time to stock your raft with supplies. What is your canonical dangerous ingredient list?

The solution to part two is the sorted list of allergen ingredients. Each allergen can be mapped to a unique ingredient by successively finding those with a single potential ingredient and removing the ingredient from the remaining allergen lists.

  my @matched-allergens = do while %possible-allergens {
      my $allergen = %possible-allergens.sort(*.value.elems).first;

      for %possible-allergens.keys -> $k {
          %possible-allergens{$k} = %possible-allergens{$k} (-) $allergen.value

      $allergen.key => $allergen.value.keys[0]

  say "Part Two";
  say @matched-allergens.sort(*.key)>>.value.join(',')
Part One
Part Two