Skip to content

Instantly share code, notes, and snippets.

@varenc
Last active September 20, 2024 21:41
Show Gist options
  • Save varenc/63e771584ece19d9b0692f84a2b910d8 to your computer and use it in GitHub Desktop.
Save varenc/63e771584ece19d9b0692f84a2b910d8 to your computer and use it in GitHub Desktop.
Varen siblings + partners gift exchange pairings for Christmas 2024
import random
import pandas as pd
from tabulate import tabulate
SIBLINGS = ["McKenzie", "Reagan", "Danni", "Alex", "Thomas", "Ben", "Shane", "Eric", "Chris"]
PARTNERS = ["Emilie", "Julia", "Courtney", "Kirstyn", "Stefanie", "Rilka"]
sibling_partner_mapping = {
"Alex": "Emilie",
"Thomas": "Julia",
"Ben": "Courtney",
"Shane": "Kirstyn",
"Eric": "Stefanie",
"Chris": "Rilka"
}
assert all(s in SIBLINGS for s in sibling_partner_mapping.keys()), "Partner-havers must be siblings"
assert all(p in PARTNERS for p in sibling_partner_mapping.values()), "Partners must be in the partners list"
def generate_advanced_pairings(siblings, partners, sibling_partner_mapping, gifts_per_sibling, sibling_to_sibling_gifts):
"""
- Each sibling gives `gifts_per_sibling` gifts
- Partners aren't assigned to give gifts
- Each sibling receives `sibling_to_sibling_gifts` gifts from other siblings
- Each partner receives the remaining gifts, distributed as evenly as possible
- Siblings don't give gifts to themselves
- Siblings don't give gifts to their own partner
- Siblings don't receive gifts from a sibling they're also giving a gift to (non-reciprocal)
"""
pairings = {s: [] for s in siblings}
total_gifts = len(siblings) * gifts_per_sibling
sibling_gifts = len(siblings) * sibling_to_sibling_gifts
partner_gifts = total_gifts - sibling_gifts
# shuffle partners to ensure randomness
random.shuffle(partners)
partner_gift_counts = {p: partner_gifts // len(partners) for p in partners}
extra_gifts = partner_gifts % len(partners)
for i in range(extra_gifts):
partner_gift_counts[partners[i]] += 1
print(f"{partner_gift_counts=}")
# generate sibling to sibling gifts ensuring non-reciprocity
for giver in siblings:
available = set(siblings) - {giver, sibling_partner_mapping.get(giver, '')}
loop_count = 0
while len(pairings[giver]) < sibling_to_sibling_gifts:
assert loop_count < 1000, f"Looped too many times on {giver=!r}! Perhaps not solvable?"
recipient = random.choice(list(available))
if giver not in pairings[recipient]:
pairings[giver].append(recipient)
available.remove(recipient)
loop_count += 1
# assign partners ensuring not to give to own partner
for giver in siblings:
potential_partners = [p for p in partners if p != sibling_partner_mapping.get(giver, '') and partner_gift_counts[p] > 0]
while potential_partners and len(pairings[giver]) < gifts_per_sibling:
selected_partner = random.choice(potential_partners)
pairings[giver].append(selected_partner)
partner_gift_counts[selected_partner] -= 1
potential_partners = [p for p in partners if p != sibling_partner_mapping.get(giver, '') and partner_gift_counts[p] > 0]
return pairings
def main():
gifts_per_sibling = 4
sibling_to_sibling_gifts = 3
pairings = generate_advanced_pairings(SIBLINGS, PARTNERS, sibling_partner_mapping, gifts_per_sibling, sibling_to_sibling_gifts)
pairings_df = pd.DataFrame.from_dict(pairings, orient='index')
pairing_len=len(list(pairings.items())[0][1])
print(f"{pairing_len=} ,{list(pairings.items())[0][1]=}")
print(tabulate(pairings_df, headers=[f"Sib Gift {x}" if x <=sibling_to_sibling_gifts else f"Partner Gift {x}" for x in range(1,pairing_len+1)], tablefmt='psql'))
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment