31

My friends invited me home to play the game of Secret Santa, where we are supposed to draw a lot & play the role of 'Santa' for a friend in the group.

So, we write all our names and pick a name randomly. If any of us ends up having their own name picked, then we reshuffle and pick names all over again (the rationale being that one can not be one's own Santa).

There are seven of us while playing so I thought of the final 'Santa-allocation' as a permutation of (1:7) onto itself, with some restrictions.

I would like to invite various ideas about how we could use Mathematica in particular or any programming language or even an algorithm to:

  • List/print out ALL the 'valid' Santa-allocations
  • Is scalable as the number of friends playing 'Secret Santa' grows
smci
  • 32,567
  • 20
  • 113
  • 146
fritz
  • 698
  • 6
  • 12
  • 2
    forgive the ignorance, but doesn't this just resolve to 7! ? Number of possibilities that is. Not the exact contents of those. – Sheriff Dec 22 '11 at 21:00
  • 3
    @Sheriff No, it doesn't. He's asking for the permutations that leave no element in place. For three elements, (123) (132) (321) (213) are rejected, (231) and (312) are okay. – Szabolcs Dec 22 '11 at 21:06
  • 1
    @Sheriff, yes, very much indeed. n! will be the total number of permutations, but, some of them will be 'invalid' & need to be considered. The simple rule is that if person 'i' picks 'i' then this 'permutation' is invalid. If 1,2,3,..n are people & P(1), P(2)..P(n) are the slots that they pick, then for every 1<=i<=n, i should not be equal to P(i). I know this is quite a simple condition, but I'm curious to learn the various 'idioms' this can be 'programmed', say in Mathematica...and see if we can find some interesting simplification/pattern... – fritz Dec 22 '11 at 21:07
  • @Szabolcs -- yes you are right! – fritz Dec 22 '11 at 21:14
  • 1
    To the close voters: this is why [Mathematica users want their own stack exchange site](http://area51.stackexchange.com/proposals/37304/mathematica). – Verbeia Dec 23 '11 at 10:24

6 Answers6

30

What you're looking for is called a derangement (another lovely Latinate word to know, like exsanguination and defenestration).

The fraction of all permutations which are derangements approaches 1/e = approx 36.8% -- so if you are generating random permutations, just keep generating them, and there's a very high probability that you'll find one within 5 or 10 selections of a random permutation. (10.1% chance of not finding one within 5 random permutations, every additional 5 permutations lowers the chance of not finding a derangement by another factor of 10)

This presentation is pretty down-to-earth and gives a recursive algorithm for generating derangements directly, rather than having to reject permutations that aren't derangements.

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
Jason S
  • 184,598
  • 164
  • 608
  • 970
  • 2
    Indeed, this was a pleasant introduction fer me to the community of stack-over-flow...! I never thought there was a special term fer such a 'deranged, & silly' (as my friends perhaps felt?!) idea I was hell-bent on chasing up... Thanks a ton fer the prompt help..! – fritz Dec 22 '11 at 22:50
  • @fritz Welcome to StackOverflow, and don't forget to accept an answer to the question (if there is a suitable one!) :-) – Szabolcs Dec 23 '11 at 08:47
16

I propose this:

f[s_List] := Pick[#, Inner[SameQ, #, s, Nor]] & @ Permutations@s

f @ Range @ 4
{{2, 1, 4, 3}, {2, 3, 4, 1}, {2, 4, 1, 3}, {3, 1, 4, 2}, {3, 4, 1, 2},
 {3, 4, 2, 1}, {4, 1, 2, 3}, {4, 3, 1, 2}, {4, 3, 2, 1}}

This is significantly faster than Heike's function.

f @ Range @ 9; //Timing
secretSanta[9]; //Timing
{0.483, Null}
{1.482, Null}

Ignoring transparency of code, this can be made several times faster still:

f2[n_Integer] := With[{s = Range@n},
    # ~Extract~ 
       SparseArray[Times@@BitXor[s, #] & /@ #]["NonzeroPositions"] & @ Permutations@s
  ]

f2[9]; //Timing
{0.162, Null}
Mr.Wizard
  • 24,179
  • 5
  • 44
  • 125
  • 1
    (1) I had a hunch that SparseArray could be used to speed this up. Nice work. (2) For what it's worth, there (appears) to be a built-in function that can give the *number* of 'derangements', but not the actual 'derangements', automatically. See the `Subfactorial` function. – telefunkenvf14 Dec 26 '11 at 01:04
  • 2
    Thanks fer these 2 'gems' @Mr.Wizard, I too loved your use of the SparseArray-- I really got to learn so much, thanks to this game! :) Happy holidays & a wonder-filled new Year to ALL..! – fritz Dec 26 '11 at 15:09
15

A permutation that maps no element to itself is a derangement. As n increases, the fraction of derangements approaches the constant 1/e. As such, it takes (on average) e tries to get a derangement, if picking a permutation at random.

The wikipedia article includes expressions for calculating explicit values for small n.

wnoise
  • 9,764
  • 37
  • 47
  • 1
    Thanks a lot for this info..! Although it appeared to be a trivial 'filtering' of some permutations from the total n! arrangements, I had some intuition that THIS ought to have some 'pattern' in it...! :) I shall try implementing some ways of 'enumerating' the derangements in Mathematica and explore..! Thanks a lot again..! – fritz Dec 22 '11 at 22:48
  • @wnoise - You point out that as n rises, "...the fraction of derangements approaches the constant 1/e." This reminds me of a general class of optimal stopping/search problems called 'secretary problems', where the same 1/e result crops up. If familiar, can you comment on the relationship between derangements and the 'secretary problem'? (I think this would be a good question to pose formally somewhere in the stack universe, but probably not on SO. Feel free to harvest the question idea if worthwhile and if answering here would be a waste of time.) – telefunkenvf14 Dec 26 '11 at 00:44
  • @telefunkenvf14: I've never heard of "secretary problems", so couldn't comment. – wnoise Dec 26 '11 at 01:33
13

In Mathematica you could do something like

secretSanta[n_] := 
  DeleteCases[Permutations[Range[n]], a_ /; Count[a - Range[n], 0] > 0]

where n is the number of people in the pool. Then for example secretSanta[4] returns

{{2, 1, 4, 3}, {2, 3, 4, 1}, {2, 4, 1, 3}, {3, 1, 4, 2}, {3, 4, 1, 2}, 
  {3, 4, 2, 1}, {4, 1, 2, 3}, {4, 3, 1, 2}, {4, 3, 2, 1}}

Edit

It looks like the Combinatorica package in Mathematica actually has a Derangements function, so you could also do something like

Needs["Combinatorica`"]
Derangements[Range[n]]

although on my system Derangements[Range[n]] is about a factor 2 slower than the function above.

Heike
  • 24,102
  • 2
  • 31
  • 45
  • 2
    Your function can be written more concisely: `secretSanta[n_] := Cases[Permutations@Range@n, a_ /; FreeQ[a - Range[n], 0]]` – Mr.Wizard Dec 24 '11 at 01:00
2

This does not answer your question about counting the valid derangements, but it gives an algorithm to generate one (which might be what you want) with the following properties:

  1. it guaranties that there is a single cycle in Santa's relationship (if you play at 4, you do not end up with 2 Santa couples --> 2 cycles),
  2. it works efficiently even with very large number of player,
  3. if applied fairly, nobody knows whose who Santa's,
  4. it does not need a computer, only some paper.

Here the algorithm:

  • Every player writes her/his name on an envelope and puts her/his name in a folded paper in the envelope.
  • One trusted player (for property # 3 above) takes all the envelopes and shuffles them looking at their back side (where no name is written).
  • Once the envelops are shuffled well enough, always looking at the back side, the trusted player moves the paper in each envelope to the following one.
  • After shuffling the envelops again, the envelopes are distributed back to the player whose name is on them, and each player is the Santa of the person whose name is in the envelope.
jfg956
  • 16,077
  • 4
  • 26
  • 34
1

I came across the built-in Subfactorial function in the documentation and altered one of the examples to produce:

Remove[teleSecretSanta];
teleSecretSanta[dims_Integer] :=
 With[{spec = Range[dims]},
  With[{
    perms = Permutations[spec],
    casesToDelete = DiagonalMatrix[spec] /. {0 -> _}},
   DeleteCases[perms, Alternatives @@ casesToDelete]
   ]
  ]

One can use Subfactorial to check the function.

Length[teleSecretSanta[4]] == Subfactorial[4]

As in Mr.Wizard's answer, I suspect teleSecretSanta can be optimized via SparseArray. However, I'm too drunk at the moment to attempt such shenanigans. (kidding... I'm actually too lazy and stupid.)

Mr.Wizard
  • 24,179
  • 5
  • 44
  • 125
telefunkenvf14
  • 1,011
  • 7
  • 19