Dag 5 was één van de moeilijkste dagen die ik heb meegemaakt. Zeker voor een “eerste week puzzel”, en dan met name de tweede ster, was dit heel lastig vond ik.

Aan de andere kant is dit soms ook een kwestie van het trucje kennen. Ik kan me voorstellen dat sommige mensen hier juist zo mee klaren waren. (Maar de dag 5 leaderboard stelt me dan ook wel weer wat gerust. 😉)

Advent of Code 2023 dag 5: de opdracht Link to heading

Het begrijpen van de opdracht is hier wel echt gelijk de eerste uitdaging.

We moeten schakelen van het ene type naar het andere (seed -> soil -> fertilizer -> … -> location), maar de invoer die we krijgen is wat vreemd.

Een stukje ziet er bijvoorbeeld zo uit:

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

Dit verzorgt de “vertaling” van seed naar soil.

De eerste cijfer-regel betekent: voor seed-waarde 98, geldt soil-waarde 50.

De 2 betekent dat we het hebben over een reeks van 2 cijfers: dus 98 -> 50, 99 -> 51.

Vergelijkbaar voor de andere regel: 50 -> 52, 51 -> 53, 52 -> 54, … 97 -> 99.

Advent of Code 2023 dag 5, ster 1 Link to heading

De eerste vraag is nog vrij simpel: wat is de laagste locatie die je bereikt na het doen van alle vertalingen?

<?php
require_once __DIR__ . '/../init.php';

$lines = array_filter($lines);

$first_line                   = $lines[0];
$the_numbers_we_need_to_check = explode(' ', explode(': ', $first_line)[1]);

$answer = PHP_INT_MAX;
foreach($the_numbers_we_need_to_check as $soil_number)
{
	$current_number = $soil_number;
	$mapping_found  = false;

	$mapping_lines  = array_slice($lines, 2);

	foreach($mapping_lines as $key => $line)
	{
		if(preg_match('/^[a-z]/', $line))
		{
			$mapping_found = false;
			continue;
		}

		if($mapping_found)
		{
			continue;
		}

		[$dest_start, $source_start, $steps] = explode(' ', $line);

		if($current_number < $source_start || $current_number > ($source_start + $steps))
		{
			$mapping_number = $current_number;
		}
		else
		{
			$difference     = $current_number - $source_start;
			$mapping_number = $dest_start + $difference;
			$current_number = $mapping_number;
			$mapping_found  = true;
		}
	}

	$answer = min($mapping_number, $answer);
}

printf("%d\n", $answer);

Advent of Code 2023 dag 5, ster 2 Link to heading

Het was vooral deel twee waar ik lang heb vast gezeten, met veel (achteraf domme) bugs.

Ik merkte dat ik veel moeite had om de vertaalslag in mijn hoofd te maken, wat het zoeken van onjuiste resultaten ook moeilijker maakte.

Maar het is gelukt.

Mijn mooiste code? Nee.

Maar het werkt. En daar laat ik het even bij voor nu.

<?php

require_once __DIR__ . '/../init.php';

function splitRange(array $range_a, array $range_b): array
{
	$output = ['left' => [], 'center' => [], 'right' => []];

	if($range_a['min'] > $range_b['max'])
	{
		$output['right'] = $range_a;
	}

	elseif($range_a['max'] <= $range_b['min'])
	{
		$output['left'] = $range_a;
	}

	elseif($range_a['min'] >= $range_b['min'] && $range_a['max'] <= $range_b['max'])
	{
		$output['center'] = ['min' => $range_a['min'], 'max' => $range_a['max']];
	}

	elseif($range_a['min'] >= $range_b['min'] && $range_a['min'] <= $range_b['max'])
	{
		$output['center'] = ['min' => $range_a['min'], 'max' => $range_b['max']];
		$output['right']  = ['min' => $range_b['max'] + 1, 'max' => $range_a['max']];
	}

	elseif($range_a['min'] < $range_b['min'] && $range_a['max'] > $range_b['min'])
	{
		$output['left']   = ['min' => $range_a['min'], 'max' => $range_b['min'] - 1];
		$output['center'] = ['min' => $range_b['min'], 'max' => $range_a['max']];
	}

	return $output;
}

function extractSeedRanges(array $lines): array
{
	$seed_numbers = explode(' ', $lines[0]);
	$seed_ranges  = [];
	$iterations   = count($seed_numbers) - 1;

	for($i = 1; $i < $iterations; $i += 2)
	{
		$min           = $seed_numbers[$i];
		$max           = $min + $seed_numbers[$i + 1] - 1;
		$seed_ranges[] = ["min" => (int)$min, "max" => (int)$max];
	}

	usort($seed_ranges, fn($a, $b) => $a["min"] <=> $b["min"]);

	return $seed_ranges;
}

function createSectionMapping(string $section, array $map, array $lines, array $section_line_numbers): array
{
	if(!isset($section_line_numbers[$section]['to']))
	{
		$section_line_numbers[$section]['to'] = count($lines);
	}

	for($i = $section_line_numbers[$section]['from']; $i < $section_line_numbers[$section]['to']; $i++)
	{
		[$min_dest, $min_source, $steps] = array_map(fn($x) => (int)$x, explode(' ', $lines[$i]));

		$max_source = $min_source + $steps - 1;
		$max_dest   = $min_dest   + $steps - 1;

		$map[$section][] = [
			'source' => ['min' => $min_source	, 'max' => $max_source],
			'dest'   => ['min' => $min_dest		, 'max' => $max_dest],
			'shift'  => $min_dest - $min_source
		];
	}

	usort($map[$section], fn($a, $b) => $a['source']["min"] <=> $b['source']["min"]);
	return $map;
}

function buildMap($lines): array
{
	$line_count      = count($lines);
	$section_line_numbers              = [];
	$current_section = '';

	for($line_number = 2; $line_number < $line_count; $line_number++)
	{
		if(!preg_match('/(\w+-to-\w+) map:/', $lines[$line_number], $matches))
		{
			continue;
		}

		$section_line_numbers[$current_section]['to'] = $line_number - 1;
		$current_section                              = $matches[1];
		$map[$current_section]                        = [];
		$section_line_numbers[$current_section]       = ['from' => $line_number + 1];
	}

	foreach(array_keys($map) as $section)
	{
		$map = createSectionMapping($section, $map, $lines, $section_line_numbers);
	}

	return $map;
}

function transformRangesUsingMap($current_seed_ranges, $section, $map): array
{
	$current_intervals = $current_seed_ranges;
	$temp_intervals  = [];
	$new_intervals   = [];

	foreach(array_column($map[$section], 'source') as $k => $section_interval)
	{
		if(empty($current_intervals))
		{
			$current_intervals = $temp_intervals;
			$temp_intervals  = [];
		}

		while($current_source_range = array_shift($current_intervals))
		{
			$range_splits = splitRange($current_source_range, $section_interval);
			foreach($range_splits as $part => $split_range)
			{
				if(empty($split_range))
				{
					continue;
				}

				if($part == 'center')
				{
					$new_intervals[] = array_map(fn($x) => $x + $map[$section][$k]['shift'], $split_range);
				}
				else
				{
					$temp_intervals[] = $split_range;
				}
			}
		}
	}

	$output = array_merge($temp_intervals, $new_intervals);
	return $output;
}

function main(array $lines): void
{
	// start with the initial seed ranges
	$current_seed_ranges = extractSeedRanges($lines);
	$map             = buildMap($lines);
	$sections        = array_keys($map);

	foreach($sections as $k => $v)
	{
		$current_seed_ranges = transformRangesUsingMap($current_seed_ranges, $v, $map);
	}
	printf("%d\n", min(array_column($current_seed_ranges, 'min')));
}
main($lines);

(En ja, ik heb even een aparte main(): void gemaakt alsof ik weer in C programmeer. Het was een makkelijke manier om af en toe even die code niet uit te laten voeren. Eigenlijk niet heel erg anders if __name__ == "__main__": in Python.)