This year, I decided to write up a program to automate our family's secret santa name drawings by sending each person an email of who they got without anyone else knowing. At first I thought this would be trivially easy, but it has led me to a problem that I don't know the efficient solution to. The program goes through each name, creates a list containing all unpicked names except for the name being assigned, and then makes a random selection. Here is my code below:
from random import choice
names = ["Foo", "Bar", "Baz"]
unassigned = names[:]
assigned = []
for gifter in names:
giftee = choice([n for n in unassigned if n != gifter])
assigned.append((gifter, giftee))
unassigned.remove(giftee)
for name, giftee in assigned:
print(f'{name} -> {giftee}')
The problem is that if the last name iterated over is also the last unpicked name, then there is no way to satisfy the needed conditions. From my testing, the code above fails about 25% of the time (at least with 3 names). What can I do, or what algorithm could I implement, in order to ensure that these edge cases are not being caused while iterating over everyone?
I've set a few rules for myself in finding a solution:
- If at all possible, I don't want to simply catch the exception and repeat until success. I'd like to find a determinate solution that guarantees success in one run.
- I'd like to keep as much entropy/randomness as possible for each individual assignment rather than generating a single, continuous chain (i.e., someone can still give to and receive from the same person). Of course, any added conditional logic will inevitably decrease this to some degree.