Advent of code 2018/7
Ajax Direct

Answer 25ms

Part 1 : BITRAQVSGUWKXYHMZPOCDLJNFE Part 2 : 869
$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);