A few days ago, my friend David asked me if I could help him with a card trick. I said I could, hence this post. I managed to pin David down in front of my camera long enough for him to demonstrate the trick; a full explanation follows this video:

The basic trick is pretty well-known: Card Colm wrote about it for his MAA column a few years ago, and collected a pretty extensive list of people who have written on the subject. David came across it through Persi Diaconis and Ron Graham’s book Magical Mathematics, but hadn’t read the book in detail before we tried to work the trick out for ourselves.

A de Bruijn sequence of length $k$ on an alphabet $A$ is a cyclic sequence which contains all possible strings of length $k$ exactly once. It follows that if you are given a string of $k$ letters, you can tell exactly where it occurs in the sequence. Since it doesn’t matter what the symbols in the alphabet $A$ are as long as they’re all different, we can just refer to a de Bruijn sequence $(k,n)$, containing all subsequences of length $k$ using the numbers $\{0,\dots,n-1\}$.

We decided we were going to assign each card in a standard deck a 6-digit binary string (because $2^6 = 64 \gt 52$) and to arrange them in the order of a $(6,2)$ de Bruijn sequence. Then, if we asked someone to cut the deck, draw out six cards and tell us their colours, we could work out exactly which cards they had in their hand.

There can be more than one de Bruijn sequence for each choice of $k$ and $n$, so we could pick the one that was easiest to construct. For binary sequences, this rule always generates a de Bruijn sequence of length $k$: write $k-1$ zeroes followed by a $1$, then subsequent letters are given by $x_{i+6} := x_i+x_{i+1} \pmod 2$ — add two adjacent numbers to get the one six places along. Because addition modulo $2$ is the same thing as subtraction modulo $2$, the same trick works backwards: $x_i = x_{i+1} + x_{i+6}$.

So the only thing left to do was to assign binary strings to cards. I decided that four digits for face value and two for suit would do, since it lets you split the calculation up into two small steps. So the face values go from $0 = A$, to $12 = K$, and the suits go Diamonds, Clubs, Hearts, Spades. In a hand of six cards drawn from the deck, the sequence of red or black colours gives the value of the last card drawn.

The only problem is that there are twelve strings representing face values greater than $12$, and I don’t think it’s possible to construct a de Bruijn sequence where they all occur in one block. But equally, because there are $64$ six-digit binary strings, any sequence we used would have twelve unused strings at the end. So we had to make one kludge and manually swap “bad” strings in the usable part of the sequence with “good” ones at the end.

Now, decoding binary strings is pretty tedious work, and finding these unusable strings is especially so, so I quickly wrote a Python script to do the calculations for me. Here it is:

# de Bruijn card trick computatorator # by Christian Perfect # based on a trick by Persi Diaconis and Ron Graham #names of cards faces = ['King','Ace','2','3','4','5','6','7','8','9','10','Jack','Queen'] suits = ['Diamonds','Clubs','Hearts','Spades'] #generate de bruijn sequence sequence = [0,0,0,0,0,1] for i in range(6,64): sequence.append( (sequence[i-6]+sequence[i-5]) % 2 ) #get a string of binary digits representing an integer def binarise(n,length): digits=[] while n>0: digits.insert(0,int(n%2)) n=(n-n%2)/2 digits = [0]*(length-len(digits))+digits #pad to the desired length return digits #decode a string of binary digits to an integer def debinarise(digits): n=0 for i in digits: n*=2 n+=i return n #decode a six-digit binary string to a card def decode_sequence(seq): number = debinarise(seq[:4]) suit = debinarise(seq[4:]) return number,suit #turn a card into a six-digit binary string def encode_card(card): number,suit = card return ''.join([str(x) for x in binarise(number,4)+binarise(suit,2)]) #display a card's binary encoding and its name def show_card(card): number,suit = card return '%s: %s of %s' % (encode_card(card), faces[number], suits[suit]) # The actual computation! sequence*=2 #take two copies of the sequence to cope with the cycle at the end # Get the ordering of the (64, not all real) cards from the sequence cards = [decode_sequence(sequence[i:i+6]) for i in range(0,64)] # The deck of cards consists of the first 52 deck = cards[:52] # Get the unused cards from the end of the 64-deck which have usable values castoffs = [(x,y) for (x,y) in cards[52:] if x<13] # Separate them into red and black castoffs_red = [(x,y) for (x,y) in castoffs if y%2==0] castoffs_black = [(x,y) for (x,y) in castoffs if y%2==1] # Get the cards from the 52-deck that don't have usable values toswap = [(x,y) for (x,y) in deck if x>=13] swaps = {} # Match up bad cards in the 52-deck with good cards in the castoffs for card in toswap: number,suit = card b=castoffs_red.pop() if suit%2==0 else castoffs_black.pop() swaps[card]=b # Show the resulting ordering of the real 52-card deck for i in range(0,52): digits = sequence[i:i+6] card = decode_sequence(digits) if card in swaps.keys(): card = swaps[card] print(show_card(card)) # Show which bad codes are swapped with which good ones print('Swaps') for a,b in swaps.items(): print('%s -> %s' % (encode_card(a),show_card(b)))

And here’s its output:

000001: Ace of Clubs 000010: Ace of Hearts 000100: 2 of Diamonds 001000: 3 of Diamonds 010000: 5 of Diamonds 100001: 9 of Clubs 000011: Ace of Spades 000110: 2 of Hearts 001100: 4 of Diamonds 011000: 7 of Diamonds 110001: King of Clubs 100010: 9 of Hearts 000101: 2 of Clubs 001010: 3 of Hearts 010100: 6 of Diamonds 101001: Jack of Clubs 010011: 5 of Spades 100111: 10 of Spades 001111: 4 of Spades 011110: 8 of Hearts 011111: 8 of Spades 000000: Ace of Diamonds 100000: 9 of Diamonds 101000: Jack of Diamonds 010001: 5 of Clubs 100011: 9 of Spades 000111: 2 of Spades 001110: 4 of Hearts 011100: 8 of Diamonds 101111: Queen of Spades 110010: King of Hearts 100100: 10 of Diamonds 001001: 3 of Clubs 010010: 5 of Hearts 100101: 10 of Clubs 001011: 3 of Spades 010110: 6 of Hearts 101101: Queen of Clubs 011011: 7 of Spades 010111: 6 of Spades 101110: Queen of Hearts 011101: 8 of Clubs 101011: Jack of Spades 110000: King of Diamonds 101100: Queen of Diamonds 011001: 7 of Clubs 110011: King of Spades 100110: 10 of Hearts 001101: 4 of Clubs 011010: 7 of Hearts 010101: 6 of Clubs 101010: Jack of Hearts Swaps 110101 -> 010101 110110 -> 110000 110111 -> 010111 111101 -> 011111 111001 -> 101111 111011 -> 101011 111010 -> 000000 110100 -> 100000

The Python script constructs the de Bruijn sequence, decodes it into a sequence of 64 cards (some of them virtual), finds bad cards in the deck of 52 and swaps them with good cards in the pile of twelve “castoffs” at the end, making sure the colour is preserved. It turns out there are eight bad cards in the deck that need swapping.

So, we only need to remember eight pairs of binary strings and a simple iterative rule which constructs the sequence in order to perform the trick. Not bad for an afternoon’s work!

References

Universal cycles for combinatorial structures by Chung, Diaconis and Graham

Products of Universal Cycles by Diaconis and Graham

Magical Mathematics: The Mathematical Ideas that Animate Great Magic Tricks by Diaconis and Graham

What’s Black and Red and Red All Over? by Colm Mulcahy