Dec 252012
 

In honor of Christmas, I wanted to put up a little script I wrote for a family holiday tradition. Every year, we each pick a name at random and fill their Christmas stocking. There’s a few rules to this little game, but the one that applies for the purposes of this script is that you cannot be assigned your own stocking. While this script randomly assigns someone a stocking, you can use the same code to dole out Secret Santa targets if that’s more your thing.

First and foremost, the script:

#/usr/bin/env python
import smtplib
import sys

from random import choice

# Pairings aren't recipricol (i.e. just because I've been assigned my mother's
# stocking doesn't mean she was assigned me. To keep everything straight, we
# need 2 lists.
family = ["Sister", "Brother-in-Law", "Father", "Me", "Grandmother", "Mother", "Fiance"]
not_chosen = ["Sister", "Brother-in-Law", "Father", "Me", "Grandmother", "Mother", "Fiance"]

# There's an outside chance that these are not real e-mail addresses
emails = {
        "Sister": "sister@email.com",
        "Brother-in-Law": "brother-in-law@email.com",
        "Father": "father@email.com",
        "Me": "me@email.com",
        "Grandmother": "grandmother@email.com",
        "Mother": "mother@email.com",
        "Fiance": "fiance@email.com"
        }
assignments = {}

# Go through each family member and give them a stocking assignment randomly.
# The stocking assignment cannot be that family member.
for member in family:

    stocking_assignment = member
    ctr = 0

    # Keep going until you pick an assignment who isn't the family member in
    # question.
    while stocking_assignment == member:
        stocking_assignment = choice(not_chosen)
        ctr += 1

        # By this point the loop's run over 100 times. If we STILL haven't
        # found an assignee that isn't the family member, we can safely
        # assume that the current family member is the only possible
        # assignment left - so let's break out of the script and try again.
        if ctr > 100 and stocking_assignment == member:
            print "INFINITE LOOP!!!! FAILZ!!!111"
            sys.exit(1)

    # We have someone who isn't the family member! 
    assignments[member] = stocking_assignment
    not_chosen.remove(stocking_assignment)

session = smtplib.SMTP("smtp.gmail.com:587")
session.starttls()

credentials = {
                  "username": "me",
                  "password": "NotGonnaTellYou"
              }

session.login(credentials["username"], credentials["password"])
message = """
        Subject: Stocking assignment

        Hi %s! The stocking drawing has occurred! You have been randomly
        assigned:\n%s
        """
# Communicate these random assignments via e-mail.
for person in emails:
    address = emails[person]
    session.sendmail(address, address, message %(person, assignments[person]))

OK, now that the whole script is out there for the time-honored tradition of copy-and-paste coding, I’ll take a minute or two to actually go through it for anyone that interested.

First, let’s set everything up:

# Pairings aren't recipricol (i.e. just because I've been assigned my mother's
# stocking doesn't mean she was assigned me. To keep everything straight, we
# need 2 lists.
family = ["Sister", "Brother-in-Law", "Father", "Me", "Grandmother", "Mother", "Fiance"]
not_chosen = ["Sister", "Brother-in-Law", "Father", "Me", "Grandmother", "Mother", "Fiance"]

# There's an outside chance that these are not real e-mail addresses
emails = {
        "Sister": "sister@email.com",
        "Brother-in-Law": "brother-in-law@email.com",
        "Father": "father@email.com",
        "Me": "me@email.com",
        "Grandmother": "grandmother@email.com",
        "Mother": "mother@email.com",
        "Fiance": "fiance@email.com"
        }
assignments = {}

I need a list of the family members participating, and a list of who still needs to have their name given as a stocking assignment. I have to tell everyone who’s stocking they have to fill, so I’m going to put together a dictionary that has everyone’s name and e-mail addresses. It’s important to note that the the family member names need to be consistent throughout all of these variables, otherwise everything just falls apart. Lastly, we’ll end with a dictionary that associates each family member with the stocking they “drew” from the cup.

Now that we’re done with that, let’s get to the meat of the script, drawing whose stocking everyone’s going to be stuffing. First, we’re going to iterate through each family member:

# Go through each family member and give them a stocking assignment randomly.
# The stocking assignment cannot be that family member.
for member in family:

    stocking_assignment = member
    ctr = 0

I’m initializing stocking_assignment to the current family member so my upcoming while loop will run at least once. ctr is something I’m using to prevent said while loop from going on infinitely. Speaking of while loop, here’s the real meat to the script:

# Keep going until you pick an assignment who isn't the family member in
# question.
while stocking_assignment == member:
    stocking_assignment = choice(not_chosen)
    ctr += 1

    # By this point the loop's run over 100 times. If we STILL haven't
    # found an assignee that isn't the family member, we can safely
    # assume that the current family member is the only possible
    # assignment left - so let's break out of the script and try again.
    if ctr > 100 and stocking_assignment == member:
        print "INFINITE LOOP!!!! FAILZ!!!111"
        sys.exit(1)

    # We have someone who isn't the family member! 
    assignments[member] = stocking_assignment
    not_chosen.remove(stocking_assignment)

First and foremost, keep looping so long as we randomly (or deliberately like I did to kick this loop off). We’re picking stocking assignments randomly, so it’s theoretically possible I try to assign someone their own stocking, so we need to keep going until that’s not a problem.

I mentioned using ctr to stop infinite loops, and here’s how. I’m tracking how many times I run the loop, and if I run that loop over 100 times and still haven’t gotten someone other than the individual we’re choosing for, that person has somehow become the only person who hasn’t had their stocking chosen, and I need to bail out and put out a message that things went south (which, oddly enough, is what I do).

Realistically speaking, I’ll find someone with a stocking to stuff the first time through, but whenever I finally get a good assignment, I update my dictionary of stocking assignments and remove the lucky stuffee from the list of stockings up for grabs.

Finally, we have to tell everyone who they “drew”:

session = smtplib.SMTP("smtp.gmail.com:587")
session.starttls()

credentials = {
                  "username": "me",
                  "password": "NotGonnaTellYou"
              }

session.login(credentials["username"], credentials["password"])
message = """
        Subject: Stocking assignment

        Hi %s! The stocking drawing has occurred! You have been randomly
        assigned:n%s
        """
# Communicate these random assignments via e-mail.
for person in emails:
    address = emails[person]
    session.sendmail(address, address, message %(person, assignments[person]))

As you probably noticed, I’m setting this up to send email via Gmail (specifically my Gmail account). If the credentials variable seems weird, I originally had them in a separate JSON document with the thought I could run the script from my laptop and then remove the file with my username and password and still post it. A dictionary is the quickest edit for that.

Now I just whip a pre-scripted message that just takes the person filling the stocking, and the stocking to be filled, and then I just loop through my dictionary of family members and email address and fire the message off.

That’s my Christmas script. I hope you guys enjoy it and find it more useful than my family did (apparently they find drawing names out of a mug cooler and more fun than running a script that can e-mail them this information, who knew?). Merry Christmas, and happy coding in the new year.

 Posted by at 7:00 AM