"""Auslosung: zufällige Zuordnung, bei der niemand sich selbst zieht und einseitige Ausschlüsse respektiert werden. Ansatz: Rejection-Sampling über zufällige Permutationen. Für typische Gruppen (Familien/Freunde, < 30 Personen) findet sich eine gültige Permutation fast immer in wenigen Versuchen (nur die Derangement- Bedingung allein hat bereits Erfolgswahrscheinlichkeit ~1/e). """ from __future__ import annotations import random MAX_ATTEMPTS = 2000 def draw_assignment( participant_ids: list[str], exclusions: set[tuple[str, str]], *, rng: random.Random | None = None, max_attempts: int = MAX_ATTEMPTS, ) -> dict[str, str] | None: """Liefert {giver_id: receiver_id} oder None, wenn keine gültige Zuordnung gefunden wurde (z. B. unerfüllbare Ausschlüsse).""" rng = rng or random.Random() ids = list(participant_ids) n = len(ids) if n < 2: return None for _ in range(max_attempts): receivers = ids[:] rng.shuffle(receivers) if any(receivers[i] == ids[i] for i in range(n)): continue mapping = dict(zip(ids, receivers)) if any((giver, receiver) in exclusions for giver, receiver in mapping.items()): continue return mapping return None