De afgelopen vier jaar heb ik meegedaan met Advent of Code. Elk jaar lijkt er weer wat mis te gaan met kerst (klinkt bekend?, en moeten er 50 sterren verzameld worden om kerst te redden.

Iedere dag wordt er een nieuw verhaal gepubliceerd, met een uitdaging. Weet je het antwoord op de puzzel te vinden? Dan krijg je een tweede vraag, vaak een iets complexere versie van de eerste.

Voor ieder goed antwoord krijg je een ster, en zo kun je op 25 december vijftig sterren hebben.

Dit jaar heb ik besloten mee te doen met het “documenteren” van mijn oplossingen. Of ik het dit jaar haal tot en met 25 december weet ik nog niet; meestal moet ik ergens afhaken omdat het te complex wordt voor mij.

(Vorig jaar moest ik stoppen omdat ik er te veel tijd aan ging besteden, en merkte dat “nog even een paar minuutjes wat proberen” steeds verder oprekte, en ten koste ging van mijn werktijd… Het was gewoon een soort verslaving geworden, hoe gek dat ook klinkt.)

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

Voor de eerste dag, de eerste ster, krijgen we een lijst met tekst-regels die er zo uit ziet:

1abc2
pqr3stu8vwx
a1b2c3d4e5f
treb7uchet

De uitdaging: haal het eerste en het laatste cijfer uit iedere regel. Combineer ze (niet optellen, gewoon “vastplakken”), en tel alle getallen dan bij elkaar op.

Bovenstaande wordt dus de totale som van 12 + 38 + 15 + 77. (De laatste regel bevat maar één cijfer, dus dat is zowel het eerste als het laatste getal.)

Meestal los ik ze op met PHP of Python, en wanneer ik PHP gebruik heb ik alvast mijn init.php klaar staan waar ik onder andere het bestand inlees in de variabele $lines. Omdat dat elke dag hetzelfde is ga ik dat niet steeds herhalen hier.

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

print array_reduce($lines, function($sum, $line){
	preg_match_all('/(\d)/', $line, $matches, PREG_PATTERN_ORDER);
	return $sum + (int)($matches[1][0] . end($matches[1]));
}, 0);

Met array_reduce doorlopen we de complete array, en voeren op elk element (elke regel) dezelfde functie uit. Deze functie krijgt een variabele mee ($sum) die geïnitaliseerd is op 0, maar steeds door de functie wordt aangepast.

In de functie gebruiken we een regular expression om te zoeken naar alles wat er uit ziet als een getal (0-9).

Dan voegen we de eerste en de laatste match match samen, en zetten dat om naar een integer.

Die tellen we steeds op bij de bestaande $sum, en zo komen we aan het eindtotaal.

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

Het tweede deel was dit jaar vrij lastig voor een dag 1, hoewel ik het zonder hints heb kunnen oplossen. Ik las wel van veel verschillende mensen dat zij hetzelfde probleem hadden als ik…

Maar eerst: de nieuwe puzzel.

Naast simpele getallen staan sommige cijfers ook uitgeschreven. Bijvoorbeeld “one” in plaats van 1. Deze moeten omgezet worden in getallen, en daarna gebruiken we dezelfde regels.

Dus one234 wordt 14.

Op zich niet héél ingewikkeld, maar toch werkte mijn oplossing wel voor de test-invoer, en niet voor de “echte” invoer.

De reden bleek in een edge case te zitten: wat als er overlap is? Bijvoorbeeld 2threeight gaf bij mij 23 (2, three), maar moest 28 (2, eight) geven.

Het ging mis omdat de “e” van “eight” al was opgesnoept door de laatste “e” van “three”.

De oplossing? Gebruik een positive lookahead assertion in de regular expression, met ?=.

Dit was een typisch geval waar het vinden van het probleem (“ah, het gaat mis met overlap!”) veel lastiger was dan het vinden van de oplossing.

De rest van de code hier is vrij eenvoudig: we maken een array om de woorden te koppelen aan cijfers via hun index, doen een iets uitgebreidere regular expression, en maken dan het totaal weer op.

Om de woorden daadwerkelijk om te zetten naar cijfers kijken we of we te maken hebben met een telwoord of een cijfer via ctype_digit. Een andere manier om dit te doen (en bij nader inzien wat mooier) was een null coalescing operator te gebruiken; iets als $words_digit_mapping[$x] ?? $x zou hier prima gewerkt hebben.

<?php
$output = 0;

$words_digits_mapping = ['null', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'];

foreach($lines as $line)
{
	preg_match_all('/(?=(\d|one|two|three|four|five|six|seven|eight|nine))/', $line, $matches, PREG_PATTERN_ORDER);

	$matches[1] = array_map(
		fn($x) => ((ctype_digit($x)) ? $x : (array_search($x, $words_digits_mapping))),
		$matches[1]
	);

	$output += (int)($matches[1][0] . end($matches[1]));
}

print $output;

Conclusie Link to heading

Tot zover Advent of Code 2023, dag 1.

Ik weet nog niet hoe ver ik ga komen met AoC, en ook niet hoe lang ik deze blog volhoud, maar het leek me in ieder geval wel een keer leuk om iets te documenteren.