function positions($string, $size) {
global $pos_cache;
$key = $string."|".$size;
if (isset($pos_cache[$key])) return $pos_cache[$key];
preg_match_all("/(?<=#{{$size}})/", str_replace("?", "#", $string), $pos, PREG_OFFSET_CAPTURE);
$pos = array_filter(array_column($pos[0], 1), function ($p) use ($string, $size) {
if (strpos(substr($string, 0, $p - $size), "#") !== false) return false;
if (($string[$p]??false) == "#") return false;
return true;
});
return $pos_cache[$key] = $pos;
}
function arrangements($string, $numbers) {
global $cache;
$key = $string."|".join(",", $numbers);
if (isset($cache[$key])) return $cache[$key];
// All numbers placed : 1 arrangement found if no # left
if (!$numbers) return strpos($string, "#") === false ? 1 : 0;
// Get the first number to place
$n = array_shift($numbers);
// Place it to all possible positions, and recurse with the rest of the number/string
$sum = 0;
foreach (positions($string, $n) as $p) {
$nstring = $string;
foreach (range($p - $n, $p - 1) as $i) $nstring[$i] = "#";
$nstring = substr($nstring, $p + 1);
$sum += arrangements($nstring, $numbers);
}
$cache[$key] = $sum;
return $cache[$key];
}
// ==================================================
// > SOLVE
// ==================================================
[$solution_1, $solution_2] = $input->lines->reduce(function ($solutions, $line) {
[$string, $counts] = $line->split(" ");
return [
// Part 1
$solutions[0] + arrangements($string, explode(",", $counts)),
// Part 2
$solutions[1] + arrangements(
implode("?", array_fill(0, 5, $string)),
array_merge(...array_fill(0, 5, explode(",", $counts)))
)
];
}, [0, 0]);