class Bingo extends Grid
{
public function isCompleted()
{
return // Compare the number of rows/columns that are not empty to the width/height of the grid
(clone $this)->rows()->callEach()->filter()->filter()->count() != $this->height()
||
(clone $this)->columns()->callEach()->filter()->filter()->count() != $this->width();
}
}
// Format the input so it's easier to work with
$input = $input->replace(["\n ", " "], ["\n", " "]);
// Extract numbers and bingo grids
$numbers = ($input = $input->split("\n\n"))->splice(0, 1);
$bingos = $input->map(fn ($grid) => (new Bingo($grid, " ")));
$completed_boards = set();
// Mark boards number by number and keep track of all the completed boards
foreach ($numbers[0]->split(",") as $number) {
foreach ($bingos as $bingo) {
if (empty($bingo->completed) && $pos = $bingo->search($number)) {
$bingo->set($pos, false);
$bingo->clearCache();
if ($bingo->completed = $bingo->isCompleted()) {
$completed_boards[] = [$bingo, $number];
if ($completed_boards->count() >= $bingos->count()) break 2;
}
}
}
}
// ==================================================
// > SOLUTIONS
// ==================================================
[$winner_board, $winner_number] = $completed_boards->first();
$solution_1 = $winner_board->cells()->sum() * $winner_number->int;
[$loser_board, $loser_number] = $completed_boards->last();
$solution_2 = $loser_board->cells()->sum() * $loser_number->int;