$req = $input->lines->map(fn ($line) => [$line[5], $line[36]])->groupBy(1)->callEach()->column(0);
$steps = $req->keys()->merge($req->values()->merge())->unique()->sort()->values();
$ready = $steps->remove($req->keys());
// ==================================================
// > PART 1
// ==================================================
function solve_1($req, $steps, $ready) {
$order = [];
while ($ready->count()) {
$step = $ready->shift();
$order[] = $step;
foreach ($req as $s=>$r) {
if ($r->remove($order)->empty()) $ready[] = $s;
}
$req = $req->removeKeys($ready);
$ready = $ready->sort()->values();
}
return implode("", $order);
}
$solution_1 = solve_1(clone $req, clone $steps, clone $ready);
// ==================================================
// > PART 2
// ==================================================
function solve_2($req, $steps, $ready) {
$working = $ready->mapAssoc(fn ($i, $s) => [$s => ord($s) - 4]);
$done = set();
$time = 0;
while (true) {
// Decrement time left, increment time spent
$working = $working->map(fn ($t) => max(0, $t - 1));
$time++;
// All done, return solution (time)
$done = $working->keep(0)->keys();
if ($done->count() >= $steps->count()) return $time;
// If a step requirement is done and we have available workers, add it to the working queue
foreach ($req as $s=>$r) {
if ($r->remove($done)->empty() && $working->removeKeys($done)->count() < 5) $working[$s] = ord($s) - 4;
}
$req = $req->removeKeys($working->keys());
}
}
$solution_2 = solve_2(clone $req, clone $steps, clone $ready);