Scripting Secret Santa

Posted: December 11th, 2011 | Author: | Filed under: Software | Tags: , , , , , | No Comments »

Every year, my closest friends and I do a ‘Secret Santa’ gift exchange. For the unfamiliar, the idea is to randomly draw the name of one other person, and to buy him or her a gag gift without giving away your identity. It’s a lot of fun, and we’ve been doing it for a number of years now.

Unfortunately, since we no longer live under the same roof (or even in the same city), it’s hard to draw names prior to the gift exchange. Of course, one person could draw all of the names and let each other participant know who to buy for, but then the person who drew the names would already know who was giving gifts to whom, thus defeating the spirit of the exercise. There are a number of websites that purport to solve the problem, but none of them are perfect.

Since I’m a giant nerd, I decided to script my own solution. I went into it with three rules in mind:

  1. Every person must receive exactly one gift.
  2. No person should draw their own name, or that of their significant other.
  3. Participants should be notified of the draw by email, without the person who runs the script ever knowing the results.

Because Python is a cool language that I’m not very experienced with, I decided to use it to hack something together. My results are shown below:

#! /usr/bin/python

import random
import smtplib
from email.mime.text import MIMEText

# a recursive function to match pairs
# the goal is to get somebody in participants to give a gift to each person in ungifted
# we don't allow people to give gifts to themselves or to their significant others
def match(participants, ungifted):
     # primary base case
     if len(ungifted) == 0:
               return True

     #processing case
     else:
          # ungifted reduces by one each recursion, 
          # so this is counting down from end of list
          first = participants[len(ungifted) - 1][0]
          so = participants[len(ungifted) - 1][1]

          # error base case
          if (len(ungifted) == 1 and (first == ungifted[0] or so == ungifted[0])):
               print "algorithm failed, please start again"
               return False

          #can't get yourself or significant other
          second = first
          while second == first or second == so:
               second = random.choice(ungifted)

          ungifted.remove(second)

          pairs.append([first,second])
          return match(participants, ungifted)

# list of participants in tuples of <participant, significant other>
participants = [['Jim','Anne'], ['Anne','Jim'], ['John','Rachel'], ['Rachel','John'], 
                    ['Joseph',None], ['Ed',None], ['Mark',None]]

# participant email addresses keyed on name
emails = {'Jim':'jim@live.ca', 'Anne':'anne@gmail.com', 'John':'john@johnsmith.net', 
              'Rachel':'rachel@hotmail.com', 'Joseph':'joe@gmail.com', 
              'Ed':'ed@live.com', 'Mark':'mark@gmail.com'}

# take the first name in each tuple
ungifted = list(couple[0] for couple in participants)

#the list of gifting tuples of <sender, recipient>
pairs = []

# perform the matches
if match(participants, ungifted):
   
     # loop through matches, sending emails
     for pair in pairs:
          you = emails[pair[0]]
          msgstr = pair[0] + ", your secret santa is " + pair[1]
          print "Sending email to ",you

          me = 'myemailaddress@mydomain.com'
          msg = MIMEText(msgstr)
          msg['Subject'] = 'Secret Santa Pairs v2.1'
          msg['From'] = me
          msg['To'] = you

          s = smtplib.SMTP('mail.mydomain.com')
          s.login('username', 'password')
          s.sendmail(me, you, msg.as_string())
          s.quit()

The vast majority of the magic takes place in the match() function. It’s a recursive algorithm that iterates over the ungifted list and randomly selects somebody from the participants list whose job it will be to purchase a gift for that person. The main downside of this approach is that it is possible to get to the last person and have no logical choice for a partner – i.e. when we try to select a partner for Jim, the only person left in the ungifted list is Anne, which violates rule #2. In that case, we fail out before sending the emails and instruct the user to run the script again.

In order to use the script, just copy it into a text file (paying very close attention to the indentation!), change the contents of the participants and emails arrays, and modify the last block to reflect your SMTP settings and email account username/password. Save the file with a .py extension, and from a terminal, run python <scriptname>.py

Happy gifting!