I have an array which tells whether a card is in use:
int used[52];
This is a terrible way to pick a random card if I have many used cards:
do {
card = rand() % 52;
} while (used[card]);
since if I have only 3-4 unused cards, it’ll take forever to find them.
I came up with this:
int card;
int k = 0;
int numUsed = 0;
for (k=0; k < 52; ++k) {
if (used[k]) numUsed += 1;
}
if (numUsed == 52) return -1;
card = rand() % (52 - numUsed);
for (k=0; k < 52; ++k) {
if (used[k]) continue;
if (card == 0) return k;
card -= 1;
}
which I guess works better if the deck is full, but works worse when the deck is empty since I have to go through two for loops.
What’s the most efficient way to do this?
I think your two-pass algorithm is likely to be the best you can do, given the constraint you added in a comment that you don’t know in advance which cards are eligible for a given draw.
You could try the cunning “select at random from a list of unknown size in a single pass” algorithm:
Then if (as you say in a comment) different draws have different criteria, you can replace
used[i]with whatever the actual criteria are.The way it works is that you select the first card. Then you replace it with the second card with probability 1/2. Replace the result with the third card with probability 1/3, etc. It’s easy to prove by induction that after n steps, the probability of each of the preceding cards being the selected one, is 1/n.
This method uses lots of random numbers, so it’s probably slower than your two-pass version unless getting each item is slow, or evaluating the criteria is slow. It’d normally be used e.g. for selecting a random line from a file, where you really don’t want to run over the data twice. It’s also sensitive to bias in the random numbers.
It’s good and simple, though.
[Edit: proof
Let p(j,k) be the probability that card number j is the currently-selected card after step k.
Required to prove: for all n, p(j,n) = 1/n for all 1 <= j <= n
For n = 1, obviously p(1,1) = 1, since the first card is selected at the first step with probability 1/1 = 1.
Suppose that p(j,k) = 1/k for all 1 <= j <= k.
Then we select the (k+1)th card at step (k+1) with probability 1/(k+1), i.e p(k+1,k+1) = 1/(k+1).
We retain the existing selection with probability k/(k+1), so for any j < k+1:
So p(j,k+1) = 1/(k+1) for all 1 <= k <= k+1
Hence, by induction, for all n: p(j,n) = 1/n for all 1 <= j <= n]