Advertisement
musifter

AoC 2022 day 24 (Perl)

Dec 24th, 2022 (edited)
1,556
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Perl 3.79 KB | Source Code | 0 0
  1. #!/usr/bin/perl
  2.  
  3. use strict;
  4. use warnings;
  5.  
  6. use List::Priority;
  7. use List::AllUtils      qw(firstidx);
  8.  
  9. $| = 1;
  10.  
  11. sub vec_sum ($$) { my ($v, $w) = @_; return ([$v->[0] + $w->[0], $v->[1] + $w->[1]]) }
  12. sub dist ($$)    { my ($p, $q) = @_; return (abs($p->[0] - $q->[0]) + abs($p->[1] - $q->[1])) }
  13.  
  14. # Including the option to wait in directions
  15. my @Dirs  = ([1,0], [0,1], [-1,0], [0,-1], [0,0]);
  16.  
  17. my @input = map {chomp; [split //]} <>;
  18.  
  19. my $start_pos = [      0, firstidx {$_ eq '.'} $input[0]->@* ];
  20. my $end_pos   = [$#input, firstidx {$_ eq '.'} $input[-1]->@*];
  21.  
  22. my $MAX_Y = scalar @input - 2;
  23. my $MAX_X = scalar $input[0]->@* - 2;
  24.  
  25. push( @input, [('#') x ($MAX_X + 2)] );     # add sentinel row to bottom
  26.  
  27. # Get hash of locations for each character in the valley
  28. my %blizz;
  29. foreach my $y (1 .. $MAX_Y) {
  30.     foreach my $x (1 .. $MAX_X) {
  31.         push( $blizz{ $input[$y][$x] }->@*, [$y,$x] );
  32.     }
  33. }
  34.  
  35. # Build two tables, one for vertical blizzards, one for horizontal
  36. # Tables are $VBlizz[ $time % (size of valley) ]{ coord } => number of blizzards
  37. # Thus giving the number of blizzards at a spot at a time (mod repeats)
  38. my @VBlizz;
  39. my @HBlizz;
  40.  
  41. for (my $time = 0; $time < $MAX_Y; $time++) {
  42.     for my $up ($blizz{'^'}->@*) {
  43.         my $y = ($up->[0] - $time - 1) % $MAX_Y + 1;
  44.         $VBlizz[$time]{$y, $up->[1]}++;
  45.     }
  46.  
  47.     for my $down ($blizz{'v'}->@*) {
  48.         my $y = ($down->[0] + $time - 1) % $MAX_Y + 1;
  49.         $VBlizz[$time]{$y, $down->[1]}++;
  50.     }
  51. }
  52.  
  53. for (my $time = 0; $time < $MAX_X; $time++) {
  54.     for my $left ($blizz{'<'}->@*) {
  55.         my $x = ($left->[1] - $time - 1) % $MAX_X + 1;
  56.         $HBlizz[$time]{$left->[0], $x}++;
  57.     }
  58.  
  59.     for my $right ($blizz{'>'}->@*) {
  60.         my $x = ($right->[1] + $time - 1) % $MAX_X + 1;
  61.         $HBlizz[$time]{$right->[0], $x}++;
  62.     }
  63. }
  64.  
  65. # Function to print the valley for testing
  66. sub print_valley {
  67.     my $time = shift @_;
  68.  
  69.     print "Time: $time\n";
  70.     print '#' x ($MAX_X + 2), "\n";
  71.     for my $y (1 .. $MAX_Y) {
  72.         print '#';
  73.         for my $x (1 .. $MAX_X) {
  74.             my $num = ($VBlizz[$time % $MAX_Y]{$y,$x} // 0)
  75.                         + ($HBlizz[$time % $MAX_X]{$y,$x} // 0);
  76.  
  77.             print( ($num == 0) ? '.' : $num );
  78.         }
  79.         print "#\n";
  80.     }
  81.     print '#' x ($MAX_X + 2), "\n";
  82. }
  83.  
  84. #
  85. # A* Search Time!
  86. #
  87. sub cross_valley {
  88.     my ($begin, $start, $end) = @_;
  89.  
  90.     my $queue = new List::Priority;
  91.     $queue->insert( 0, [$begin, $start] );
  92.  
  93.     my $count = 0;
  94.     my %visit;
  95.  
  96.     QUEUE:
  97.     while (my ($time, $pos) = ($queue->shift)->@*) {
  98.         print ::stderr "[$time : ", $queue->size(), "] ($pos->[0],$pos->[1])  \r"  if ($count++ % 10000 == 0);
  99.  
  100.         if ($pos->[0] == $end->[0] && $pos->[1] == $end->[1]) {
  101.             print ::stderr "\nAcross at $time\n";
  102.             return ($time);
  103.         }
  104.  
  105.         # Circling back here later might be correct.
  106.         # Coming back at the exact same time, no.
  107.         next QUEUE  if ($visit{$time, $pos->[0], $pos->[1]}++);
  108.  
  109.         MOVE:
  110.         foreach my $d (@Dirs) {
  111.             my $npos = vec_sum( $pos, $d );
  112.             next MOVE  if (exists $VBlizz[($time + 1) % $MAX_Y]{$npos->[0], $npos->[1]}
  113.                             or exists $HBlizz[($time + 1) % $MAX_X]{$npos->[0], $npos->[1]});
  114.  
  115.             next MOVE  if ($input[$npos->[0]][$npos->[1]] eq '#');
  116.  
  117.             # Add distance to end as heuristic (minimal number of steps)
  118.             $queue->insert( $time + 1 + &dist($npos, $end), [$time + 1, $npos] );
  119.         }
  120.     }
  121. }
  122.  
  123. my $time = &cross_valley( 0, $start_pos, $end_pos );
  124.  
  125. print "Part 1: $time\n";
  126.  
  127. # Silly elf!  Next time don't forget your snacks!
  128. $time = &cross_valley( $time, $end_pos, $start_pos );
  129. $time = &cross_valley( $time, $start_pos, $end_pos );
  130.  
  131. print "Part 2: $time\n";
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement