I have been doing a little recreational holiday computing. My mini-project was a simulation of the Italian game of "tomboli". A key building block was a simulation of the following process;
The game is controlled by a man with a bag of 90 marbles, numbered 1 to 90. He draws marbles one by one randomly from the bag, each time calling out the marble number to the players.
After a little thought I wrote the following code for this building block;
// NBR marbles, numbered 1...NBR are in a bag. Simulate randomly
// pulling them from the bag, one by one, until the bag is empty
void bag( int random_sequence[NBR] )
{
int i;
// Store each marble as it is pulled out
int *store = random_sequence;
// Array of marbles still in the bag
int not_yet_pulled[NBR];
for( i=0; i<NBR; i++ )
not_yet_pulled[i] = i+1; // eg NBR=90; 1,2,3 ... 90
// Loop pulling marbles from the bag, one each time through
for( i=NBR; i>=1; i-- )
{
int x = rand();
int idx = x%i; // eg i=90 idx is random in range 0..89
// eg i=89 idx is random in range 0..88
// ...
// eg i=1 idx is random in range 0..0
// (so we could optimize when i=1 but not
// worth the bother)
*store++ = not_yet_pulled[idx];
// Replace the marble just drawn (so it cannot be pulled again)
// with the last marble in the bag. So;
// 1) there is now one less marble in the bag
// 2) only marbles not yet pulled are still in the bag
// If we happened to pull the last marble in the bag, this is
// not required but does no harm.
not_yet_pulled[idx] = not_yet_pulled[i-1];
}
}
I know there are subtleties and traps all over the place in game simulation with random numbers, so although I am pretty happy with my code my confidence is a little less than 100%. So my questions are;
1) Is there anything wrong with my code ?
2) [if the answer to 1) is no] Am I unknowingly using a standard shuffling algorithm ?
3) [if the answer to 2) is no] How does my algorithm compare to standard alternatives ?
EDIT Thanks to all who answered. I am going to accept Aidan Cully's answer because it turns out I was rediscovering the Fisher-Yates algorithm, and revealing that gets to the heart of the matter. Of course it is no surprise I could have saved myself time and effort by doing some research up front. But on the other hand it was a fun hobby project. The rest of the simulation was routine, this was the most interesting part, and I would have deprived myself of enjoyment by not having a go myself. Additionally, I was trying to simulate a man taking marbles from a bag, and it was fairly late in the piece that I realized that the situation was exactly analagous to shuffling cards.
Another point of interest is that there is a small flaw, identified by Ken who points out that the oft repeated pattern rand()%N is not a really good way of picking a random number from the range 0..N-1.
Finally, my version of Fisher-Yates lacks the elegant trick that allows the nice property of shuffling in place. As a result, my algorithm would end up with an equally random but reversed shuffle.