AoC 2023 D5P1: Integer Ranges

Advent of Code day 5 has us processing ranges of integers. Given seed values and lists of translations in the form dest src length, find the lowest value after applying all the translations.

seeds: 79 14 55 13

seed-to-soil map:
50 98 2
52 50 48

soil-to-fertilizer map:
0 15 37
37 52 2
39 0 15

...

I went over the top on this problem, loading all of the translations into memory as though they were important, then iterated through seeds in my outer loop and translations looping inside that on each seed.

Parsing the File

sub getinput {
$/ = "\n\n";

while (<>) {
chomp;

$/ is Perl’s input record separator, newline by default. If you set it to something else, each read of a record (line) of input will be separated/ terminated by this record separator. I set it to two newlines to grab a paragraph of input at a time. Then chomp is aware and discards the trailing input record separator (the trailing pair of newlines, not just the last trailing newline).

@seed = /(\d+)/g, next if /^seeds/;

Perl’s regular expression operators work fine on multi-line strings (and have some special modifiers available that I don’t need here). So this line watches for the input paragraph that begins with seeds, grabs ( ) strings of digits \d+ out of it, all of them /g, returns that list, and stores it in the array @seed.

s/(\S+)\s+map:\n//; my $which = $1;
foreach (split /\n/) {
my @n = split /\s+/;
push @{$map{$which}}, { D => $n[0], S => $n[1], L => $n[2] };
}

If a paragraph starts with some kind of map, delete that line of the paragraph, grabbing the map name out of it along the way. Then split the rest of the paragraph into lines; split each line on whitespace (also could have done my @n = /(\d+)/g again but I’m fickle); and treat the %map hash element keyed by $which as an array, pushing an anonymous hash { } onto it keyed mnemonically by Dest, Source, and Length, allowing rapid retrieval of these values later by symbol rather than merely by ordinal position as they were given to us in the puzzle input.

Mapping Values by Range Translations

sub mapval {
my ($src, $lref) = @_;

I want to call this subroutine with parameters: a source (seed) value and an array (list) reference to a set of range translations. Subroutine parameters arrive in the special array @_, so break them out to variables with (vaguely) meaningful names.

my $mapped;
foreach my $href (@$lref) {
$mapped = $$href{D} - $$href{S} + $src
if $src >= $$href{S} && $src <= $$href{S} + $$href{L} - 1;
}

Go through the list @$lref of range translations pointed at by the array reference, each of which is a hash reference. If our seed value is within the range (between $$href{S} and $$href{S} + $$href{L} - 1), then map it by applying the offset between the translation's destination and source.

One could do a last here to avoid testing further translations; but there aren't many per map and each seed will only match one translation per map; so I didn't bother and let it cycle through testing the remaining ranges.

return $mapped || $src;

If we applied a translation, return that, otherwise return the seed value.

Clumsily Calling It All

my $min;
foreach my $seed (@seed) {
my $soil = mapval($seed, $map{"seed-to-soil"});
my $fert = mapval($soil, $map{"soil-to-fertilizer"});
my $water = mapval($fert, $map{"fertilizer-to-water"});
my $light = mapval($water, $map{"water-to-light"});
my $temp = mapval($light, $map{"light-to-temperature"});
my $humid = mapval($temp, $map{"temperature-to-humidity"});
my $loc = mapval($humid, $map{"humidity-to-location"});

Since I'm never going to need these maps again, this was an oddly careful way of setting them up and using them.

I could of course have written this as a deeply-nested series of subroutine calls instead of sequentially capturing all of the intermediate values; but I was expecting to have to debug this (didn't) and wanted the intermediate values at hand for inspection.

Full Program

#!/usr/bin/perl

use warnings;
use strict;

sub getinput;
sub mapval;

my (@seed, %map);
getinput;

my $min;
foreach my $seed (@seed) {
my $soil = mapval($seed, $map{"seed-to-soil"});
my $fert = mapval($soil, $map{"soil-to-fertilizer"});
my $water = mapval($fert, $map{"fertilizer-to-water"});
my $light = mapval($water, $map{"water-to-light"});
my $temp = mapval($light, $map{"light-to-temperature"});
my $humid = mapval($temp, $map{"temperature-to-humidity"});
my $loc = mapval($humid, $map{"humidity-to-location"});

$min = $loc if !defined $min || $loc < $min;
}

print "minimum location $min\n";

sub getinput {
$/ = "\n\n";

while (<>) {
chomp;
@seed = /(\d+)/g, next if /^seeds/;

s/(\S+)\s+map:\n//; my $which = $1;
foreach (split /\n/) {
my @n = split /\s+/;
push @{$map{$which}}, { D => $n[0], S => $n[1], L => $n[2] };
}
}
}

sub mapval {
my ($src, $lref) = @_;

my $mapped;
foreach my $href (@$lref) {
$mapped = $$href{D} - $$href{S} + $src
if $src >= $$href{S} && $src <= $$href{S} + $$href{L} - 1;
}

#print "$src:\t", $mapped ? $mapped : "unmapped", "\n";
return $mapped || $src;
}

Leave a Reply