Raku
My solution for today is quite sloppy. For part 2, I chose to color along both sides of the path (each side different colors) and then doing a fill of the empty space based on what color the empty space is touching. Way less optimal than scanning, and I didn’t cover every case for coloring around the start point, but it was interesting to attempt. I ran into a bunch of issues on dealing with nested arrays in Raku, I need to investigate if there’s a better way to handle them.
Edit: did some cleanup, added some fun, and switched to the scanning algorithm for part 2, shaved off about 50 lines of code.
Code
use v6;
sub MAIN($input) {
my $file = open $input;
my @map = $file.lines».comb».Array;
my @starting-point = @map».grep('S', :k)».[0].grep(*.defined, :kv).List;
my @path = (@starting-point,);
my %tile-neighbors =
'|' => (( 1, 0),(-1, 0)),
'-' => (( 0,-1),( 0, 1)),
'L' => ((-1, 0),( 0, 1)),
'J' => ((-1, 0),( 0,-1)),
'7' => (( 1, 0),( 0,-1)),
'F' => (( 1, 0),( 0, 1)),
;
sub connecting-neighbor(@position, @neighbor) {
my @neighbor-position = @position Z+ @neighbor;
return False if any(@neighbor-position Z< (0, 0));
return False if any(@neighbor-position Z> (@map.end, @map.head.end));
my $neighbor-tile = @map[@neighbor-position[0]; @neighbor-position[1]];
my @negative-neighbor = @neighbor X* -1;
return %tile-neighbors{$neighbor-tile}.grep(@negative-neighbor, :k).elems > 0;
}
# replace starting-point with the appropriate pipe
my @start-tile-candidates = <| - L J 7 F>;
for @start-tile-candidates -> $candidate {
next if %tile-neighbors{$candidate}.map({!connecting-neighbor(@starting-point, $_)}).any;
@map[@starting-point[0]; @starting-point[1]] = $candidate;
last;
}
repeat {
my @position := @path.tail;
my $tile = @map[@position[0]; @position[1]];
my @neighbors = %tile-neighbors{$tile}.List;
for @neighbors -> @neighbor {
my @neighbor-position = @neighbor Z+ @position;
next if @path.elems >= 2 && @neighbor-position eqv @path[*-2];
if connecting-neighbor(@position, @neighbor) {
@path.push(@neighbor-position);
last;
}
}
} while @path.tail !eqv @path.head;
my $part-one-solution = (@path.elems / 2).floor;
say "part 1: {$part-one-solution}";
my %pipe-set = @path.Set;
my %same-side-pairs = ;
my $part-two-solution = 0;
for ^@map.elems -> $y {
my $inside = False;
my $entrance-pipe = Nil;
for ^@map.head.elems -> $x {
if %pipe-set{$($y, $x)} {
given @map[$y; $x] {
when '|' { $inside = !$inside }
when 'F' | 'L' { $entrance-pipe = $_ }
when 'J' | '7' {
$inside = !$inside if %same-side-pairs{$entrance-pipe} ne $_;
$entrance-pipe = Nil;
}
}
} elsif $inside {
$part-two-solution += 1;
}
}
}
say "part 2: $part-two-solution";
}
Raku
Today I’m thankful that I have the combinations() method available. It’s not hard to implement combinations(), but it’s not particularly interesting. This code is a bit anachronistic because I solved part 1 by expanding the universe instead of contracting it, but this way makes the calculations for part 1 and part 2 symmetric. I was worried for a bit there that I’d have to do some “here are all the places where expansion happens, check this list when calculating distances” bookkeeping, and I was quite relieved when I realized that I could just use arithmetic.
edit: Also, first time using the Slip class in Raku, which is mind bending, but very useful for expanding/contracting the universe and generating the lists of galaxy coordinates. And I learned a neat way to transpose 2D arrays using
[Z]
.View the code on Github
Code
use v6; sub MAIN($input) { my $file = open $input; my @map = $file.lines».comb».Array; my @galaxies-original = @map».grep("#", :k).grep(*.elems > 0, :kv).rotor(2).map({($_[0] X $_[1]).Slip}); my $distances-original = @galaxies-original.List.combinations(2).map({($_[0] Z- $_[1])».abs.sum}).sum; # contract the universe @map = @map.map: {$_.all eq '.' ?? slip() !! $_}; @map = [Z] @map; @map = @map.map: {$_.all eq '.' ?? slip() !! $_}; @map = [Z] @map; my @galaxies = @map».grep("#", :k).grep(*.elems > 0, :kv).rotor(2).map({($_[0] X $_[1]).Slip}); my $distances-contracted = @galaxies.List.combinations(2).map({($_[0] Z- $_[1])».abs.sum}).sum; my $distances-twice-expanded = ($distances-original - $distances-contracted) * 2 + $distances-contracted; say "part 1: $distances-twice-expanded"; my $distances-many-expanded = ($distances-original - $distances-contracted) * 1000000 + $distances-contracted; say "part 2: $distances-many-expanded"; }