Unique Random Numbers

The goal is to write code that generates random numbers, but never the same value twice. After all, if you’re coding a card game or generating lotto picks, it’s unrealistic to expect the same value appear multiple times.

Last week’s Lesson discussed how to create a virtual deck of cards and draw from the deck. The problem with that code was that the same “card” can be drawn more than once. In fact, given the nature of computer generated pseudo random numbers, the program could generate a hand of five cards, all of them the 7 of clubs. For a single deck, that’s not what the user expects to see.

To keep track of random values, such as lotto balls or cards in a deck, create an array. The array needs to have as many elements as there are actual lotto balls or cards. So:

int lotto_balls[6];

Or:

int deck_of_cards[52];

Each element in the array represents a real lotto ball or card. That’s how you keep track of them. (And it’s only one way to solve the problem, but bear with me.)

To make the array useful, it must be initialized. In my code, I file each element with the value 0, which is interpreted in C as FALSE:

for(x=0;x<CARDS;x++)
    deck[x] = 0;

The FALSE means, “This item has yet to be chosen,” and it works well with the programming logic that checks to see whether an item has been chosen.

During the program (game), a random value is generated. That value is in the same range as the array, so it handily maps out as an array element number. When the value 18 is drawn, for example, the code makes a note of it. I use this technique:

deck_of_cards[18] = 1;

So the value 18 is now taken, the 18th element in the array is changed; 1 replaces the 0. Of course, the code doesn’t use 18 as an immediate value. If the random value is in variable r, the code looks like this:

deck_of_cards[r] = 1;

When the same value comes up again, the code checks the array to see whether or not that value has already been drawn. In fact, that check takes place before the above statement, just to ensure that the value hasn’t already been produced. Such code could look like this:

if (deck_of_cards[r] == 1)
    printf("Card has been drawn");
else
{
    deck_of_cards[r] = 1;
    printf("Card has been drawn");
}

After each random number is generated, the above test takes place. If the value has been drawn, the array holds a 1, or TRUE condition. Otherwise, the array holds 0, FALSE, and the random number is a valid draw. The number is used, but also its position in the array is set to 1, ensuring that the number won’t be drawn again.

To make the decision structure work, it must appear in a loop. After all, if a positive is generated, meaning the number has already been produced, you need to fetch another random value.

Below you find a modification to the code presented in last week’s blog post. This modification uses the techniques discussed in this lesson to confirm that the value drawn is unique:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define CARDS 52
#define DRAW 5

void showcard(int card);

int main()
{
    int deck[CARDS];
    int x,c;

/* initialize the deck */
    for(x=0;x<CARDS;x++)
        deck[x] = 0;

    srand((unsigned)time(NULL));
    for(x=0;x<DRAW;x++)
    {
        for(;;)                 /* loop until a valid card is drawn */
        {
            c = rand() % CARDS;     /* generate random drawn */
            if(deck == 0)        /* has card been drawn? */
            {
                deck = 1;        /* show that card is drawn */
                showcard(c);        /* display card */
                break;              /* end the loop */
            }
        }                       /* repeat loop until valid card found */
    }

    return(0);
}

/* Take values 0 through 51 (cards in a deck) and display
   corresponding card name */
void showcard(int card)
{
    char *suit[4] = { "Spades", "Hearts", "Clubs", "Diamonds" };

    switch(card%13)
    {
        case 0:
            printf("%2s","A");
            break;
        case 10:
            printf("%2s","J");
            break;
        case 11:
            printf("%2s","Q");
            break;
        case 12:
            printf("%2s","K");
            break;
        default:
            printf("%2d",card%13+1);
    }
    printf(" of %s\n",suit[card/13]);
}

In my C All-In-One book, I use a similar technique, but it lives within a while loop. That’s often the subject of confusion, but the loop is essentially the same as the loop above, from Line 22 to Line 31. That for loop could be replaced with this do-while loop:

        do
        {
            c = rand() % CARDS;     /* generate random drawn */
        }
        while(deck);             /* repeat until valid number drawn */
        deck = 1;                /* show that card is drawn */
        showcard(c);                /* display card */

I believe this solution is more elegant; it avoids the break statement. The loop has to spin at least once, which is okay. But the effect is that the random value c is generated before the while test, which is what you want.

The while condition causes the loop to continue if the value drawn has already been drawn; when deck == 1, the loop repeats. Otherwise a value value has been fetched, and it can be used, as shown above.

This solution is presented in my book, but of all the programs presented it gets the most questions. Hopefully this information helps clear up the issue.

Leave a Reply