Advent of code 2015/13
Ajax Direct

Answer

Part 1 :
Part 2 :
// Define which person to sit next
$graph = new Graph(function ($graph) {
    $seated = explode(">", $graph->current);
    $person = $seated[count($seated) - 1];
    $left   = $graph->people->removeKeys($seated);

    // No more people : close the loop with Alice
    if ($left->empty() && $person != "Alice") {
        return [
            $graph->current . ">" . "Alice" =>
            $graph->people[$person]["Alice"] + $graph->people["Alice"][$person]
        ];
    }

    // Pruning : if we can't go beyond the best relation yet, stop
    $best_arrangement = empty($graph->values) ? 0 : max($graph->values);
    $max_left = $left->callEach()->values()->merge()->max();
    if (($graph->values[$graph->current] ?? 0) + $left->count() * $max_left * 2 < $best_arrangement) return [];

    return $left->mapAssoc(function ($next, $relation) use ($person, $graph) {
        return [
            $graph->current . ">" . $next =>
            $graph->people[$next][$person] + $graph->people[$person][$next]
        ];
    });

}, "highest");

// Parse relations
$graph->people = set();
foreach ($input->lines as $line) {
    preg_match("/^(\w+) would (gain|lose) (\d+) happiness units by sitting next to (\w+).$/", $line, $matches);
    $graph->people[$matches[1]][$matches[4]] = (int) ($matches[2] == "gain" ? $matches[3] : $matches[3] * -1);
}


// ==================================================
// > PART 1
// ==================================================
// Start with Alice (doesn't matter who, it's a loop)
$solution_1 = $graph->explore("Alice")[1];

// ==================================================
// > PART 2
// ==================================================
// Add yourself
$graph->people = $graph->people->mapAssoc(fn ($person, $relations) =>
    [$person => array_merge($relations, ["Me" => 0])]
);

$graph->people["Me"] = $graph->people->mapAssoc(fn ($person, $relations) =>
    [$person => 0]
);

$solution_2 = $graph->explore("Alice")[1];