Fun With Coding: Can You Unshuffle a Deck of Cards?

in #education4 years ago

A Programming Mythbusters adventure!

image.png
Image from Wikipedia
Ever hear someone say "If you shuffle it more than seven times, you're just putting it back in order"? I don't know how true that is, but I began to wonder if there was some point at all where the cards would in fact go back in order.

The Challenge

With 52 cards, is this possible? I will attempt tonight to write a program that does all this math for us to solve this problem. And I will be writing in C#.

Immediately, the first problems for success that come to mind are:

  • shuffles are not always perfect
  • initial cuts are not always perfect
  • which side of the split deck will drop first?

The Testing

Simple Test

Well in a perfect shuffle where the bottom half of the deck drops first, we get this:

image.png

Um...
The deck is back in order.
Well, I guess myth kinda confirmed...

Start with the other hand

Let's swap it around next and see how many times it'll take when the top half of the cut drops first...

image.png

It comes together near the 26th shuffle. On the 26th, it's in backwards order. It should take another 26 to get back in forwards order.

image.png

Yep.

Adding complexity

But lets be realistic here. Us normal folk can't shuffle perfect every time, and the first card that comes down may come from the former top half some times or the former bottom half. So let's add some complexity to this and see if we can't bust-ish this myth with a more realistic view. Points to consider:

  • first card down won't necessarily always come from the same hand
  • some number of cards will come down each time, not always just one

Ok let's start with the alternating first card. That'll be easy.

Here I just added in a random variable random and used it to determine which side dropped the first card. I had to use random.Next(0,2) instead of random.Next(0,1) because in C# the random starts at the first number and ends just before the second. This effectively picks a random number between 0 and 1.99 and then chops off the decimal, giving you a result of 0 or 1. Following that is my shuffling code.

Random random = new Random();
...
int[] newDeck = Shuffle(firstHalf, secondHalf, random.Next(0,2));
...
private static int[] Shuffle(int[] a, int[] b, int first)
{
    int[] newShuffle = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
            
    if (first == 0)
    {
        for (int x = 0; x < newShuffle.Length; x += 2)
        {
            newShuffle[x] = a[(int) x / 2];
            newShuffle[x + 1] = b[(int) x / 2];
        }
    }
    else
    {
        for (int x = 0; x < newShuffle.Length; x += 2)
        {
            newShuffle[x] = b[(int) x / 2];
            newShuffle[x + 1] = a[(int) x / 2];
        }
    }
    return newShuffle;
}

In these results, 52 shuffles didn't do anything spectacular. On the 27th shuffle, the 1 card and the 52 card were back in place. But then I started noticing a peculiar pattern. If you add up the first number and the last number of any shuffle, it comes out to 53 (1 plus 52 for example). Same with card 2 and card 51. Same with card 26 and card 27. each opposing pair adds up to 53. While the card positions are different throughout the shuffles, the pairs always stick together at opposite ends of the deck! It's crazy. on shuffle 8, the first card is 30 and the last card is 23. On shuffle 40, the numbers 30 and 23 are in positions 21 and 32, both 5 cards from the middle pair. (by the way, their positions will also add up to 53.)

But let's see if we can't find out how many shuffles it will take to get back in order (I'll include backwards order too). To make the output less cumbersome, I only wrote to console if the first 2 cards AND the last two cards were in order.

image.png

As you can see, I got nowhere close in the first 5000 shuffles.

Maybe in 50,000?

image.png

No closer. Not even in 50,000 shuffles did the third card ever get in place.

Ok, but one final thing to do. Like I said earlier, we're not gonna shuffle a deck this perfectly every time. More than one card may drop. I might have to rewrite my shuffler to make this happen.

To change the cut, I added another random rand.Next(-3,4) to modify the location where the split happens. Then I had to put my shuffler into a while loop and drop one card at a time from the bottom up just like you were shuffling it by hand.

private static int[] Shuffle(int[] deck, int first)
{
    int[] newShuffle = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
            
    int[] a;
    int[] b;
    Random rand = new Random();
    Split(deck, (deck.Length / 2 + rand.Next(-3, 4)), out a, out b);
    int cardsInA = a.Length;
    int cardsInB = b.Length;
    int cardsUsed = 0;
    int drop = 0;

    while (cardsInA>0 || cardsInB>0)
    {
        if (first == 0)
        {
            drop = GetCardsDropped();
            for (int x = 0; x < drop; x++)
            {
                if (cardsInA > 0)
                {
                    newShuffle[51 - cardsUsed] = a[cardsInA - 1];
                    cardsInA--;
                    cardsUsed++;
                }
            }
            drop = GetCardsDropped();
            for (int x = 0; x < drop; x++)
            {
                if (cardsInB > 0)
                {
                    newShuffle[51 - cardsUsed] = b[cardsInB - 1];
                    cardsInB--;
                    cardsUsed++;
                }
            }
         }
        else
        {   
            drop = GetCardsDropped();
            for (int x = 0; x < drop; x++)
            {
                if (cardsInB > 0)
                {
                    newShuffle[51 - cardsUsed] = b[cardsInB - 1];
                    cardsInB--;
                    cardsUsed++;
                }
            }
            drop = GetCardsDropped();
            for (int x = 0; x < drop; x++)
            {
                if (cardsInA > 0)
                {
                    newShuffle[51 - cardsUsed] = a[cardsInA - 1];
                    cardsInA--;
                    cardsUsed++;
                }
            }
        }
    }
            
    return newShuffle;
}

To randomize the number of cards that dropped, I tried to make it feel a little more natural where 1 was most likely, 2 a little less, and so on up to 5 cards.

private static int GetCardsDropped()
{
    Random rand = new Random();
    int f = rand.Next(0, 3);
    if (f == 1)
        return rand.Next(1, 4);
    else if (f == 2)
        return rand.Next(1, 6);
    else
        return 1;
}

And after 9 shuffles, we have a much nicer random assortment of cards.

image.png

Now let's see how many in 50,000 shuffles come close to being in order. I'll be more lenient and just check the first 3 cards.

image.png

This is the best I've gotten so far. Some shuffles turned up one or two, some turned up nothing.

Wait, I may not be being fair. The first few cards may be off, but that doesn't mean parts aren't in order. How about we check for trends to see if we may have a series of numbers in order somewhere other than the beginning. I wrote another function to check for these trends:

private static bool cardTrend(int[] newDeck, int v)
{
    int direction = 0;    // 0 = start. 1 = up. 2 = down.
    int trend = 0;           // how many in a row are up or down
    for (int x = 0;x<newDeck.Length-1;x++)
    {
        if (newDeck[x + 1] == newDeck[x] + 1)
        {
            if (direction == 0 || direction == 1)
            {
                direction = 1;
                trend++;
            }
            else
            {
                direction = 2;
                trend = 1;
            }
        }
        else if (newDeck[x + 1] == newDeck[x] - 1)
        {
            if (direction == 0 || direction == 2)
            {
                direction = 2;
                trend++;
            }
            else
            {
                direction = 1;
                trend = 1;
            }
        }
        else
        {
            direction = 0;
            trend = 0;
        }
      }
    if (trend >= v)
         return true;
    else
        return false;
}

And then I call it with this.

if (cardTrend(newDeck, 4))
{
    Console.WriteLine("Shuffle  " + (x + 1) + ": " + WriteDeck(newDeck));
    Console.WriteLine("");
}

And in several rounds of 50,000 shuffles, I have net been able to find a trend 5 cards long. Not even 4 cards long.

Error Checking

But wait, I found a little error in my trending code. I was trying to find a trend of 4 or more cards in order (in either direction), but I was clearing it out each time it changed direction. This returned zero results almost every time.

To resolve this issue, I added in a new variable in my CardTrend function called maxTrend. Whenever a trend ends, if the current trend length is longer than maxTrend, then maxTrend gets that value. Then reset trend back to 1. (oops. It's the little things...)

if (trend > maxTrend)
    maxTrend = trend;
trend = 1;

Now I'm getting trend results. After a couple sets of 50,000 shuffles, I got a nice result.

image.png

Here you can see several trends after the first shuffle, but then not a one until over 23,000 shuffles go by. Then not again until the 40,000's where we have 3 shuffles quite close together with trends. I highlighted the trends in yellow for easier finding. You will notice only once do we have a 6-card trend.

The Conclusion

Unless you're a card shuffler robot or professional, you probably will never successfully "unshuffle" your deck. But with enough practice, you could get good enough in your shuffle to only let one card down at a time. If you can do that regularly, then you will be able to unshuffle your deck. You would have to really work at it to do it, but I guess it could be done. But for 99% of us, I seriously doubt it's even possible to get this good of a result, let alone over 50,000 shuffles in...

Because of the amount of practice involved, I'm gonna give this myth...

Implausible (but technically true)

Thank you for reading. If you enjoyed this coding adventure and would like me to do more, give me an upvote and maybe even a comment with a suggestion for another programming mythbusters episode.

Sort:  

Upvoted by GITPLAIT!

We have a curation trial on Hive.vote. you can earn a passive income by delegating to @gitplait
We share 80 % of the curation rewards with the delegators.


To delegate, use the links or adjust 10HIVE, 20HIVE, 50HIVE, 100HIVE, 200HIVE, 500HIVE, 1,000HIVE, 10,000HIVE, 100,000HIVE


Join the Community and chat with us on Discord let’s solve problems & build together.