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 *current subarray*, 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.
Use the Fisher-Yates-Knuth shuffle:
public static void shuffle(int[] array)
{
Random rng = new Random(); // java.util.Random.
// n is the number of items left to shuffle
for (int n = array.length; n > 1; n--)
{
// Pick a random element to move to the end
int k = rng.nextInt(n); // 0 <= k <= n - 1.
// Simple swap of variables
int tmp = array[k];
array[k] = array[n - 1];
array[n - 1] = tmp;
}
}
It looks like your code might work, but I'm not sure. It's more obfuscated than the standard algorithm.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With