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; } }