[$rules, $mine, $others] = $input->split("\n\n");
// Parse rules : hashmap of valid numbers for each field
$rules = $rules->lines->mapAssoc(function ($i, $rule) {
[$name, $ranges] = $rule->split(": ");
$ranges = $ranges->split(" or ")->map(fn ($r) => $r->range);
return [(string) $name => (array) $ranges->merge()];
});
// Parse cards
$mine = $mine->lines[1]->numbers;
$others = $others->lines->slice(1)->map(fn ($l) => $l->numbers);
// ==================================================
// > PART 1
// ==================================================
$all_rules = $rules->merge();
$invalid_nums = $invalid_cards = set();
foreach ($others as $i=>$nums) {
$invalid = $nums->remove($all_rules);
if ($invalid->count()) {
$invalid_nums = $invalid_nums->merge($invalid);
$invalid_cards[] = $i;
}
}
$solution_1 = $invalid_nums->sum();
// ==================================================
// > PART 2
// ==================================================
$others = $others->removeKeys($invalid_cards)->values();
$fields = array_fill_keys((array) $mine->keys(), (array) $rules->keys());
// Restrict possibilites
foreach ($fields as $i=>$ff) {
foreach ($ff as $fi=>$f) {
foreach ($others as $nums) {
if (!in_array($nums[$i], $rules[$f])) {
unset($fields[$i][$fi]);
continue 2;
}
}
}
}
// Select fields by process of elimination
$selection = [];
while (true) {
foreach ($fields as $i=>$field) {
if (count($field) == 1) {
$selection[$i] = $field = array_values($field)[0];
unset($fields[$i]);
foreach ($fields as $j=>$f) $fields[$j] = array_diff($fields[$j], [$field]);
continue 2;
}
}
break;
}
$solution_2 = $mine->keepKeys(
set($selection)->filter(fn ($f) => substr($f, 0, 9) == "departure")->keys()
)->multiply();