Advertisement
musifter

AoC 2024, day 21, part 2 (Perl)

Dec 21st, 2024
95
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Perl 2.77 KB | Source Code | 0 0
  1. #!/usr/bin/perl
  2.  
  3. use strict;
  4. use warnings;
  5.  
  6. use feature         qw(say state);
  7. use List::Util      qw(reduce min sum);
  8.  
  9. use Math::Vector::Real;
  10.  
  11. my ($vy,$vx) = Math::Vector::Real->canonical_base(2);
  12. my %Dirs = ('v' => $vy, '>' => $vx, '^' => -$vy, '<' => -$vx);
  13.  
  14. # Chain iterator:
  15. sub chain (&@) {
  16.     my $block = shift;
  17.     return (map { &$block( $_[$_-1], $_[$_] ) } (1 .. $#_));
  18. }
  19.  
  20. #
  21. # Get locations of keys on the pads
  22. #
  23. my @keypad = map {[split //]} ('789X', '456X', '123X', 'X0AX', 'XXXX');
  24. my @dirpad = map {[split //]} ('X^AX', '<v>X', 'XXXX');
  25.  
  26. sub process_pad {
  27.     my %ret;
  28.     foreach my $y (0 .. $#_) {
  29.         foreach my $x (0 .. $_[0]->$#*) {
  30.             next if ($_[$y][$x] eq 'X');
  31.             $ret{$_[$y][$x]} = V($y,$x);
  32.         }
  33.     }
  34.     return (%ret);
  35. }
  36.  
  37. my %key_loc = &process_pad( @keypad );
  38. my %dir_loc = &process_pad( @dirpad );
  39.  
  40. #
  41. # Build routes between keys
  42. #
  43. sub route_pad {
  44.     my ($loc, $pad) = @_;
  45.     my %table;
  46.  
  47.     foreach my $start (keys %$loc) {
  48.         KEY:
  49.         foreach my $end (keys %$loc) {
  50.             if ($start eq $end) {
  51.                 $table{$start,$end} = ['A'];
  52.                 next KEY;
  53.             }
  54.  
  55.             my $delta = $loc->{$end} - $loc->{$start};
  56.             my @steps = (($delta->[0] < 0) ? '^' : 'v', ($delta->[1] < 0) ? '<' : '>');
  57.             my @queue = ([$loc->{$start}, '']);
  58.  
  59.             QUEUE:
  60.             while (my $state = shift @queue) {
  61.                 my ($pos, $path) = @$state;
  62.                 if ($pos == $loc->{$end}) {
  63.                     push( $table{$start, $end}->@*, $path . "A" );
  64.                     next QUEUE;
  65.                 }
  66.  
  67.                 foreach my $d (@steps) {
  68.                     my $move = $pos + $Dirs{$d};
  69.                     push(@queue, [$move, $path.$d]) if ($pad->[$move->[0]][$move->[1]] ne 'X');
  70.                 }
  71.             }
  72.         }
  73.     }
  74.  
  75.     return (%table);
  76. }
  77.  
  78. my %dir_route = &route_pad( \%dir_loc, \@dirpad );
  79. my %key_route = &route_pad( \%key_loc, \@keypad );
  80.  
  81. #
  82. # Mainline
  83. #
  84.  
  85. # Set to 2 for part 1
  86. my $Max_Depth = 25;
  87.  
  88. sub recurse_keypad {
  89.     my ($seq, $depth) = @_;
  90.     state %memo;
  91.  
  92.     my $routes = ($depth == 0) ? \%key_route : \%dir_route;
  93.     my @seq = ('A', split( //, $seq ));
  94.  
  95.     $memo{$seq,$depth} //= ($depth == $Max_Depth)
  96.                             ? sum chain { length( $routes->{$_[0],$_[1]}->[0] ) } @seq
  97.                             : sum chain {
  98.                                   reduce {
  99.                                       min($a, &recurse_keypad($b, $depth+1))
  100.                                   } (~0, $routes->{$_[0],$_[1]}->@*)
  101.                               } @seq;
  102. }
  103.  
  104. my @input = map {chomp; $_} <>;
  105. say "Part 2: ", sum map { int(substr($_,0,-1)) * &recurse_keypad($_, 0) } @input;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement