views:

1323

answers:

6

Every Christmas we draw names for gift exchanges in my family. This usually involves mulitple redraws until no one has pulled their spouse. So this year I coded up my own name drawing app that takes in a bunch of names, a bunch of disallowed pairings, and sends off an email to everyone with their chosen giftee.

Right now, the algorithm works like this (in pseudocode):

function DrawNames(list allPeople, map disallowedPairs) returns map
    // Make a list of potential candidates
    foreach person in allPeople
        person.potentialGiftees = People
        person.potentialGiftees.Remove(person)
        foreach pair in disallowedPairs
            if pair.first = person
               person.Remove(pair.second)

    // Loop through everyone and draw names
    while allPeople.count > 0
        currentPerson = allPeople.findPersonWithLeastPotentialGiftees
        giftee = pickRandomPersonFrom(currentPerson.potentialGiftees)
        matches[currentPerson] = giftee
        allPeople.Remove(currentPerson)
        foreach person in allPeople
            person.RemoveIfExists(giftee)

    return matches

Does anyone who knows more about graph theory know some kind of algorithm that would work better here? For my purposes, this works, but I'm curious.

EDIT: Since the emails went out a while ago, and I'm just hoping to learn something I'll rephrase this as a graph theory question. I'm not so interested in the special cases where the exclusions are all pairs (as in spouses not getting each other). I'm more interested in the cases where there are enough exclusions that finding any solution becomes the hard part. My algorithm above is just a simple greedy algorithm that I'm not sure would succeed in all cases.

Starting with a complete directed graph and a list of vertex pairs. For each vertex pair, remove the edge from the first vertex to the second.

The goal is to get a graph where each vertex has one edge coming in, and one edge leaving.

+4  A: 

I wouldn't use disallowed pairings, since that greatly increases the complexity of the problem. Just enter everyone's name and address into a list. Create a copy of the list and keep shuffling it until the addresses in each position of the two lists don't match. This will ensure that no one gets themselves, or their spouse.

As a bonus, if you want to do this secret-ballot-style, print envelopes from the first list and names from the second list. Don't peek while stuffing the envelopes. (Or you could just automate emailing everyone thier pick.)

There are even more solutions to this problem on this thread.

Bill the Lizard
That would be way easier!
Eclipse
The program just sends off an email to everyone, so the secrecy issue isn't a problem.
Eclipse
That was one of the options I mentioned.
Bill the Lizard
+4  A: 

Hmmm. I took a course in graph theory, but simpler is to just randomly permute your list, pair each consecutive group, then swap any element that is disallowed with another. Since there's no disallowed person in any given pair, the swap will always succeed if you don't allow swaps with the group selected. Your algorithm is too complex.

Brian
The person and their spouse would both be disallowed, so the swap isn't guaranteed to work.
Bill the Lizard
Not true, because the group selected would have both the person and their spouse (otherwise no swap would be needed).
Brian
A: 

Hmmm, we just had this question the other day.

DOK
Almost, but I'm interested in the selection process, not the protocol. As stated in the question, a simple email works for me.
Eclipse
+2  A: 

Just make a graph with edges connecting two people if they are allowed to share gifts and then use a perfect matching algorithm. (Look for "Paths, Trees, and Flowers" for the (clever) algorithm)

Watson Ladd
That's what I was looking for! Thanks!
Eclipse
Not quite: it's a directed graph, and you don't necessarily want a perfect matching; you just want a subgraph such that every vertex has indegree = outdegree = 1 -- a *cycle cover*. [There are ways to reduce it to a perfect matching problem, but there are also ways to solve it directly.]
ShreevatsaR
@ShreevatsaR: The graph doesn't have to be directed. Just flip a coin to choose a direction for each cycle. (This is assuming that all blacklisted pairs are symmetric.)
Chris Conway
P.S. Treating the graph as undirected rules out any 2-cycles, which is probably desirable.
Chris Conway
+1  A: 

I was just doing this myself, in the end the algorithm I used doesn't exactly model drawing names out of a hat, but it's pretty damn close. Basically shuffle the list, and then pair each person with the next person in the list. The only difference with drawing names out of a hat is that you get one cycle instead of potentially getting mini subgroups of people who only exchange gifts with each other. If anything that might be a feature.

Implementation in Python:

import random
from collections import deque
def pairup(people):
    """ Given a list of people, assign each one a secret santa partner
    from the list and return the pairings as a dict. Implemented to always
    create a perfect cycle"""
    random.shuffle(people)
    partners = deque(people)
    partners.rotate()
    return dict(zip(people,partners))
wxs
+1  A: 

Hey.

I just created a web app that will do exactly this - http://www.secretsantaswap.com/

My algorithm allows for subgroups. It's not pretty, but it works.

Operates as follows:
1. assign a unique identifier to all participants, remember which subgroup they're in
2. duplicate and shuffle that list (the targets)
3. create an array of the number of participants in each subgroup
4. duplicate array from [3] for targets
5. create a new array to hold the final matches
6. iterate through participants assigning the first target that doesn't match any of the following criteria:
     A. participant == target
     B. participant.Subgroup == target.Subgroup
     C. choosing the target will cause a subgroup to fail in the future (e.g. subgroup 1 must always have at least as many non-subgroup 1 targets remaining as participants subgroup 1 participants remaining)
     D. participant(n+1) == target (n +1)
If we assign the target we also decrement the arrays from 3 and 4

So, not pretty (at all) but it works. Hope it helps,

Dan Carlson

dancarlson