Secret Santa in R
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 Leslie Chris
## 2 Alex Dick
## 3 Susan Harry
## 4 Sam Tom
## 5 Tom Alex
## 6 Chris Sam
## 7 Jane Jane
## 8 Dick Susan
## 9 Harry Leslie
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, Alex gives to Chris even though Chris already gives to Alex.)
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 to 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!