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