#!/usr/bin/env perl6 use v6.c; class Node { has Int $.x; has Int $.y; has Int $.size; has Int $.used; has Int $.avail; method Str { "[$!x,$!y:$!size/$!used/$!avail]" } method gist { self.Str } method infix:(Node $a, Node $b) { $a.x == $b.x && $a.y == $b.y } method WHICH { "Node|$!x|$!y" } method is-empty { $!used == 0 } # Note that IRL, it may or may not fit if self.avail == $n.used (in TB) method fits(Node $n) { $!avail >= $n.used } method blocks(Node $n) { $!used > $n.avail } method adjacent-to(Node $n) { abs($!x-$n.x) + abs($!y-$n.y) == 1 } method add-data(Int $data) { die "No space on {self} for $data of data!" if $data > $!avail; $!used += $data; $!avail -= $data; } method move-data-to(Node $n) { die "Nodes {self} and $n are not adjacent!" unless self.adjacent-to($n); die "Data on {self} doesn't fit on $n" if self.blocks($n); my $ret = "Move data from {self} to $n"; $n.add-data($!used); $!avail += $!used; $!used = 0; $ret ~= " ({self}; $n)"; return $ret; } } sub MAIN(IO() $inputfile where *.f, Bool :v(:$verbose) = False) { # Read nodes my @nodes; for $inputfile.lines -> $line { next unless $line ~~ / ^ '/dev/' /; my ($node, $size, $used, $avail, $use) = $line.words; my ($x, $y) = $node.comb(/\d+/)».Int; for $size, $used, $avail -> $s is rw { # Remove 'T' postfix. Assumes all sizes are in TB. $s = $s.subst('T','').Int; } @nodes[$x;$y] = Node.new(:$x, :$y, :$size, :$used, :$avail); } # Part 1 my $viable = 0; for @nodes[*;*] -> $n { next if $n.is-empty; $viable += @nodes[*;*].grep({ $_ ne $n && $_.fits($n) }); } say "There are $viable viable pairs of nodes."; # Part 2 # This algorithm does NOT work in all cases, but IF it gives an answer, it SHOULD be the shortest path, # if the following assumptions are true: # - There is exactly one empty node # - The empty node is involved in all moves, i.e. no data can be moved onto a node already in use # - There are no blocking nodes (that can't be moved onto the empty node) in rows 0 and 1 # - All blocking nodes are in a single contiguous horizontal "wall" from x0,y to x1,y # - All non-blocking nodes are of similar sizes, and can hold each others data my $grid-width = +@nodes[*;0]; my $grid-height = +@nodes[0;*]; # Find the empty node my @empty-nodes = @nodes[*;*].grep(*.is-empty); if (!@empty-nodes) { die "Unable to find an empty node!"; } elsif (@empty-nodes > 1) { warn "Warning: more than one empty node found, using the first one"; } my $empty = @empty-nodes[0]; # Find all nodes that block the empty node - i.e. can't be moved into the empty node my @blocking-nodes = @nodes[*;*].grep(*.blocks($empty)); # Assumption: this is a solid horizontal wall that we need to pass. my ($wall-x0, $wall-x1) = (@blocking-nodes».x.min, @blocking-nodes».x.max); my ($wall-y0, $wall-y1) = (@blocking-nodes».y.min, @blocking-nodes».y.max); warn "Blocking nodes not in a horizontal wall" if $wall-y1 > $wall-y0; warn "Blocking nodes not in an unbroken horizontal wall" if any(($wall-x0..$wall-x1).map({ @nodes[$_;$wall-y0] })) ∉ @blocking-nodes; # We need to pass left or right of the wall, take the shortest route my $target-x = $empty.x; if $wall-y0 < $empty.y { if $wall-x0 > $empty.x || $wall-x1 < $empty.x { $target-x = $empty.x; } elsif $wall-x1 < $grid-width-1 { $target-x = $wall-x1+1; } elsif $wall-x0 > 0 { $target-x = $wall-x0-1; } else { die "Unable to pass blocking wall at y = $wall-y0, x = $wall-x0 - $wall-x1"; } } my @moves; # Move empty cell to x == target-x for $empty.x ... $target-x -> $x { next if $x == $empty.x; @moves.push: @nodes[$x;$empty.y].move-data-to($empty); $empty = @nodes[$x;$empty.y]; } # Now move it to y == 0 for $empty.y ... 0 -> $y { next if $y == $empty.y; @moves.push: @nodes[$empty.x;$y].move-data-to($empty); $empty = @nodes[$empty.x;$y]; # If we're at the extreme right border, we need to take a step left when y == 1 if $empty.x == $grid-width-1 && $empty.y == 1 { @moves.push: @nodes[$empty.x-1;1].move-data-to($empty); $empty = @nodes[$empty.x-1;1] } } # And move it to x == grid-width-2 for $empty.x ... $grid-width-2 -> $x { next if $x == $empty.x; @moves.push: @nodes[$x;$empty.y].move-data-to($empty); $empty = @nodes[$x;$empty.y]; } # Now we're in position to move the data from the upper right to the upper left corner. # - Move data into empty cell # - Move empty cell around data cell # - Repeat until x == 0 my $data = @nodes[$grid-width-1;0]; while $data.x > 0 { @moves.push: $data.move-data-to($empty); ($data, $empty) = ($empty, $data); last if $data.x == 0; @moves.push: @nodes[$empty.x;1].move-data-to($empty); $empty = @nodes[$empty.x;1]; @moves.push: @nodes[$empty.x-1;1].move-data-to($empty); $empty = @nodes[$empty.x-1;1]; @moves.push: @nodes[$empty.x-1;1].move-data-to($empty); $empty = @nodes[$empty.x-1;1]; @moves.push: @nodes[$empty.x;0].move-data-to($empty); $empty = @nodes[$empty.x;0]; } say ''; @moves».say if $verbose; say "It takes @moves.elems() moves to gain access to the required data."; }