Note that using % results in distributions that are not perfectly uniform due to the
pigeon-hole principle. I think Lavavej mentions this in his talk.
But assuming that you do have access to a uniform distribution source, if you are trying to exclude M particular solutions from your distribution of size N, one sort of simple way is to generate a distribution of size (M - N), and then skip over the excluded numbers when doing the mapping. This would allow you to still just make a single random call per trial. But each mapping takes O(M) to compute. For large M, using a std::map would be preferable as that would be O(log(M)) per trial.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
|
// Example program
#include <iostream>
#include <string>
#include <cstdlib>
#include <ctime>
int distribution_with_exclusions(const int Range, const int Exclusions[], const int NumExclusions)
{
const int RollSize = Range - NumExclusions;
int roll = rand() % RollSize + 1; // note: +1 here because OP was doing 1-based ranges, e.g. [1, 10]
// Also, warning: Like I said, rand()%N is inherently NOT uniform for most values of N
int offset = 0;
for (int i = 0; i < NumExclusions; i++)
{
if (roll + offset >= Exclusions[i])
{
offset++;
}
}
return roll + offset;
}
int main()
{
const int N = 10;
const int M = 2;
const int exclusions[M] = {4, 7}; // (note: will be 1-based)
// output distribution, including the zeroed out numbers
int output_dist[N] {};
for (int i = 0; i < 100000; i++)
{
int roll = distribution_with_exclusions(N, exclusions, M); // still 1-based!
if (roll - 1 < 0 || roll - 1 > N - 1)
std::cout << "error: Role out of bounds of expected distribution!\n";
output_dist[roll - 1]++;
}
for (int i = 0; i < N; i++)
{
// +1 here is because OP did 1-based distribution.
std::cout << (i+1) << " : " << output_dist[i] << '\n';
}
}
|
Nothing excluded:
1 : 10072
2 : 10005
3 : 9954
4 : 10132
5 : 10007
6 : 10045
7 : 9869
8 : 9959
9 : 9953
10 : 10004 |
{4, 7} excluded:
1 : 12503
2 : 12437
3 : 12474
4 : 0
5 : 12608
6 : 12446
7 : 0
8 : 12777
9 : 12432
10 : 12323 |
{4, 8} excluded:
1 : 12503
2 : 12437
3 : 12474
4 : 0
5 : 12608
6 : 12446
7 : 12777
8 : 0
9 : 12432
10 : 12323 |
I think the math works out here. Or, use the C++11 <random> libraries, which, while more verbose, offer a wider solution of random distributions and aren't based off rand().
Edit: Actually, each trial could be O(1) constant time if you pre-computed an offset table for each possible value of the distribution. e.g. {0, 0, 0, 1, 1, 2, 2, 2, 2, 2}.