Advertisement
musifter

AoC 2022, day 19 (Perl)

Dec 19th, 2022
2,029
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Perl 2.88 KB | Source Code | 0 0
  1. #!/usr/bin/perl
  2.  
  3. use strict;
  4. use warnings;
  5.  
  6. use List::AllUtils      qw(onlyidx pairwise all indexes max sum product reduce);
  7.  
  8. $| = 1;
  9.  
  10. sub vec_sum ($$)   { my ($v, $w) = @_; return ([pairwise {$a + $b} @$v, @$w]); }
  11. sub vec_minus ($$) { my ($v, $w) = @_; return ([pairwise {$a - $b} @$v, @$w]); }
  12. sub great_eq ($$)  { my ($v, $w) = @_; return (all {$_} pairwise {$a >= $b} @$v, @$w); }
  13.  
  14. my @Materials = ('ore', 'clay', 'obsidian', 'geode');
  15.  
  16. # Read in Blueprints
  17. my %Blueprint;
  18. foreach my $line (map { chomp; [split /[:.]/] } <>) {
  19.     my ($num) = (shift @$line) =~ m#Blueprint (\d+)#;
  20.     foreach (@$line) {
  21.         my ($type, $ore, $other) = m#(\w+) robot costs (\d+) ore(?: and (\d+))?#;
  22.         my $idx = onlyidx {$_ eq $type} @Materials;
  23.  
  24.         $Blueprint{$num}[$idx][0]  = int($ore);
  25.         $Blueprint{$num}[$idx][$_] = 0  foreach (1 .. 3);
  26.         $Blueprint{$num}[$idx][$idx - 1] = int($other) if ($idx > 1);
  27.     }
  28. }
  29.  
  30. sub run_blueprint {
  31.     my ($num, $time_limit) = @_;
  32.  
  33.     my $best = -1;
  34.     my $top_crackers = 0;
  35.     my $bp = $Blueprint{$num};
  36.     my $max_need = reduce {[map {max($a->[$_], $b->[$_])} (0 .. 3)]} ([0,0,0,~0], @$bp);
  37.  
  38.     my $old = 0;
  39.     my %visit;
  40.     my @queue = ([0, [1,0,0,0], [0,0,0,0]]);
  41.  
  42.     STATE:
  43.     while (my $state = shift @queue) {
  44.         my ($time, $robots, $mats, %passed) = @$state;
  45.  
  46.         if ($time > $old) {
  47.             print ::stderr "[$num] Time $time (queue: ", scalar @queue, ")   \r";
  48.             $old = $time;
  49.         }
  50.  
  51.         if ($time == $time_limit) {
  52.             $best = max( $best, $mats->[3] );
  53.             next STATE;
  54.         }
  55.  
  56.         # ASSUME: catching up/getting ahead from 2 geode crackers behind isn't going to happen
  57.         next STATE if ($visit{"@$robots:@$mats"}++ or $robots->[3] < $top_crackers - 1);
  58.  
  59.         # Possible builds: have resources, less than max number needed, not passed last turn
  60.         my @builds = indexes {great_eq( $mats, $_ )} @$bp;
  61.         @builds = grep { $robots->[$_] < $max_need->[$_] and !exists $passed{$_} } @builds;
  62.  
  63.         $mats = vec_sum( $mats, $robots );
  64.  
  65.         foreach my $rob (reverse @builds) {
  66.             my $new_mats = vec_minus( $mats, $bp->[$rob] );
  67.             my @new_robots = @$robots;
  68.             $new_robots[$rob]++;
  69.  
  70.             push( @queue, [$time + 1, \@new_robots, $new_mats] );
  71.  
  72.             $top_crackers = max( $top_crackers, $new_robots[3] ) if ($rob == 3);
  73.         }
  74.  
  75.         # Wait for something new, keep track of what we're passing on
  76.         push( @queue, [$time + 1, $robots, $mats, map {$_ => 1} @builds] );
  77.     }
  78.  
  79.     print ::stderr "[$num] Best: $best                                     \n";
  80.     return ($best);
  81. }
  82.  
  83. my $part1 = sum map { $_ * &run_blueprint($_, 24) } sort {$a <=> $b} keys %Blueprint;
  84. print "\nPart 1: $part1\n";
  85.  
  86. my $part2 = product map { &run_blueprint($_, 32) } (1 .. 3);
  87. print "Part 2: $part2\n";
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement