106 lines
3.3 KiB
PHP
106 lines
3.3 KiB
PHP
<?php
|
|
namespace App\Services;
|
|
|
|
use App\Models\Participant;
|
|
use Illuminate\Support\Collection;
|
|
|
|
class MatcherService
|
|
{
|
|
public function match(): void
|
|
{
|
|
$participants = Participant::all();
|
|
|
|
if ($participants->count() < 2) {
|
|
return;
|
|
}
|
|
|
|
// desperate denotes a user A that is supposed to give to a user B that dropped out
|
|
// whilst user A has a user C giving to them, or vice versa
|
|
$desperate = $participants->where('desperate', true)->values();
|
|
$nonDesperate = $participants->where('desperate', false)->values();
|
|
|
|
$desperate = $desperate->shuffle();
|
|
$nonDesperate = $nonDesperate->shuffle();
|
|
|
|
$assignments = [];
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| 1. Match DESPERATE participants
|
|
|--------------------------------------------------------------------------
|
|
| A desperate user should match:
|
|
| - First to another desperate participant
|
|
| - If none left, match to a non-desperate participant
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
foreach ($desperate as $p) {
|
|
// Exclude self from possible options
|
|
$pool = $desperate->filter(fn($d) => $d->id !== $p->id);
|
|
|
|
if ($pool->isEmpty()) {
|
|
$pool = $nonDesperate;
|
|
}
|
|
|
|
if ($pool->isEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
$candidate = $this->pickValidTarget($p, $pool, $assignments);
|
|
if ($candidate) {
|
|
$assignments[$p->id] = $candidate->id;
|
|
}
|
|
}
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| 2. Match NON-DESPERATE participants
|
|
|--------------------------------------------------------------------------
|
|
| A non-desperate participant always matches to the non-desperate pool.
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
foreach ($nonDesperate as $p) {
|
|
// Exclude self
|
|
$pool = $nonDesperate->filter(fn($d) => $d->id !== $p->id);
|
|
|
|
if ($pool->isEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
$candidate = $this->pickValidTarget($p, $pool, $assignments);
|
|
if ($candidate) {
|
|
$assignments[$p->id] = $candidate->id;
|
|
}
|
|
}
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| 3. Write all results back to DB
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
foreach ($assignments as $id => $givingId) {
|
|
Participant::where('id', $id)->update([
|
|
'giving_id' => $givingId,
|
|
]);
|
|
}
|
|
}
|
|
|
|
private function pickValidTarget(Participant $giver, Collection $pool, array $assignments): ?Participant
|
|
{
|
|
$shuffled = $pool->shuffle();
|
|
|
|
// Avoid duplicates
|
|
foreach ($shuffled as $candidate) {
|
|
if (($assignments[$candidate->id] ?? null) === $giver->id) {
|
|
continue;
|
|
}
|
|
|
|
return $candidate;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|