Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Our office just exchanged presents for Secret Santa, a tradition where each person is randomly assigned someone else to give an anonyous gift. One of the challenges of Secret Santa is keeping the pairs of gift-givers and receivers both random and secret. How can you do this while also taking part yourself? Using R, of course!

Firstly, recruit people! Write their names down, one per line, in a text file. The order doesn’t matter. For example, we might have a file called santa_names.txt, with contents as follows:

Tom
Dick
Harry
Jane
Leslie
Susan
Alex
Sam
Chris

Then read these names into R.

names <- readLines('santa_names.txt')

We now have a character vector called names.

The key to a truly random pairing of gift givers and recipients is that it shouldn’t depend on any systematic order, such as the order people signed up, or the alphabetical order of their names.

Here is one way we might try to do it. First, randomly reorder the names. Then, have every person in this list give to the next person on the list, with the very last person then giving to the first.

names2 <- sample(names)
data.frame(sender = names2,
recipient = c(tail(names2, -1), names2[1]))
##   sender recipient
## 1 Leslie      Alex
## 2   Alex     Susan
## 3  Susan       Sam
## 4    Sam       Tom
## 5    Tom     Chris
## 6  Chris      Jane
## 7   Jane      Dick
## 8   Dick     Harry
## 9  Harry    Leslie

In R, the sample() function draws randomly from a vector, and by default it does this without replacement, so we don’t have to worry about missing anybody off or somebody appearing twice. We can use it as a quick way to reorder the list of names at random. The tail command with argument -1 will select every element in the list except the first.

This works in the sense that everybody gives and receives one gift and nobody gives to themselves, but is not entirely random or secret. If you find out that Susan gave to Leslie, then you know for a fact that Leslie didn’t give to Susan. With enough of these titbits of information you could reconstruct the entire list. Ideally we want no discernable pattern.

Why not just randomly sample twice from the names list?

data.frame(sender = sample(names),
recipient = sample(names))
##   sender recipient
## 1  Chris      Dick
## 2   Jane     Harry
## 3   Alex       Sam
## 4    Tom       Tom
## 5   Dick    Leslie
## 6  Susan     Susan
## 7  Harry     Chris
## 8    Sam      Alex
## 9 Leslie      Jane

There’s a problem. Jane has been assigned to herself! That won’t do! The solution is quite straightforward, though: just keep sampling until this doesn’t happen. We can keep the order of senders fixed and vary the order of the recipients until nobody gives to themselves, like so.

sender <- sample(names)
recipient <- sample(names)
i <- 1
while (any(sender == recipient)) {
i <- i + 1
recipient <- sample(names)
}
data.frame(sender, recipient)
##   sender recipient
## 1  Chris      Alex
## 2   Jane       Tom
## 3   Alex     Chris
## 4    Tom    Leslie
## 5   Dick     Susan
## 6  Susan       Sam
## 7  Harry      Jane
## 8    Sam      Dick
## 9 Leslie     Harry

Success! Everybody gives and receives a gift, nobody gives to themselves and there is no particular pattern. (Unlike the first method, Sam gives to Leslie even though Leslie already gives to Sam.)

If you’re curious, you can find out how many attempts it took to find a valid set of pairs:

i
## [1] 2

Now, how do you let everybody know who they have been assigned, without revealing it anybody else? (Including you!) We can write a text file for every person, with the name of the file corresponding to the giver and the contents of the file revealing the recipient.

for (i in seq_along(names)) {
writeLines(recipient[i], paste0(sender[i], '.txt'))
}

If you were feeling really clever, you could automate the e-mail sending from R as well, but it should be easy enough to attach and send the files by hand without peeking.

Putting it all together:

names <- readLines('santa_names.txt')
sender <- sample(names)
recipient <- sample(names)
while (any(sender == recipient)) {
recipient <- sample(names)
}
for (i in seq_along(names)) {
writeLines(recipient[i], paste0(sender[i], '.txt'))
}

Merry Christmas!