// Parsing : direct links between caves
$caves = $input->lines->reduce(function ($carry, $line) {
[$from, $to] = $line->split("-");
$carry[(string) $from][] = (string) $to;
$carry[(string) $to][] = (string) $from;
return $carry;
}, set())->instanciate("Set");
function solve($caves, $part2 = false) {
return (new Graph(function ($graph) use ($caves, $part2) {
$path = set(explode(",", $graph->current));
$pos = $path->last();
if ($pos == "end") return [];
$forbidden = $path->filter(fn ($c) => $c[0] >= "a" && $c[0] <= "z");
if ($part2 && !$forbidden->occurrences()->remove([1])->count()) {
$forbidden = ["start"];
}
$next = [];
foreach ($caves[$pos]->remove($forbidden) as $n) {
$next[$graph->current . "," . $n] = 1;
}
return $next;
}, "highest"))
// Number of path that end with "end"
->onCompletion(function ($graph) {
return set($graph->values)
->filter(fn ($v, $k) => set(explode(",", $k))->last() == "end", true)
->count();
})
// Gogogo
->explore("start");
}
// ==================================================
// > SOLVE
// ==================================================
$solution_1 = solve($caves);
$solution_2 = solve($caves, true);