Axoy
Axoy

Reputation: 67

Can I exclude a number or subrange of numbers inside a range of random numbers in modern C++?

I have:

std::random_device rd;
std::mt19937 mt(rd());
std::uniform_int_distribution<int> probability(0, 100);

I want to exclude some numbers in this range of probabilities.
Example 1: Let's say, I want to generate a random number between 0 and 100, but this number can never be 4.
Example 2: Let's say, I want to generate a random number between 0 and 100, but this number can never be any number between 4 and 7.

I wonder if it is possible to achieve in modern C++ without using std::rand?

Upvotes: 5

Views: 1006

Answers (5)

yielduck
yielduck

Reputation: 171

You can use a filter of arbitrary complexity on uniform distribution:

template<typename D, typename G, typename F>
auto sample(D &distribution, G &generator, F const &filter)
{
    while(true)
    {
        auto const value = distribution(generator);
        if(filter(value))
            return value;
    }
}

Your example case transforms into the following

std::random_device rd;
std::mt19937 mt(rd());
std::uniform_int_distribution<int> probability(0, 100);

auto const filter = +[](int n) {return n < 4 || n > 7;}
int const i = sample(probability, mt, filter);

You have to keep in mind that this kind of filtering comes at a cost.

Let N be the number of distinct values the distribution returns, F - the number of these values filtered out; then, if you need to sample S values, you have to sample and filter S * N / (N - F) values at average. It's okay if F is small compared to N, but horribly inefficient when F approaches N. In your case, N = 100, F = 4, and N / (N - F) = 1.04166...

If you care prefer readability and simplicity, that's your choice. Otherwise, if you need performance, you'd better try out piecewise distributions or mess with the value range manually.

Upvotes: 2

Marek R
Marek R

Reputation: 38092

Example2: Let's say, I want to generate a random number in between 0 and 100, but this number can never be any number between 4 and 7.

This is what std::piecewise_constant_distribution is for.

    std::vector<int> i{0,  4, 8, 101};
    std::vector<int> w{ 4,  0,  93};
    std::piecewise_constant_distribution<> d(i.begin(), i.end(), w.begin());

Live demo

Upvotes: 5

463035818_is_not_an_ai
463035818_is_not_an_ai

Reputation: 123084

If you want to stay with a uniform_int_distribution you can do it manually like this:

Example1: Let's say, I want to generate a random number in between 0 and 100, but this number can never be 4.

std::random_device rd;
std::mt19937 mt(rd());
std::uniform_int_distribution<int> distribution(0,99);
auto temp = distribution(mt);
auto random_number = (temp < 4) ? temp : temp + 1;

Example2: Let's say, I want to generate a random number in between 0 and 100, but this number can never be any number between 4 and 7.

std::random_device rd;
std::mt19937 mt(rd());
std::uniform_int_distribution<int> distribution(0,96);
auto temp = distribution(mt);
auto random_number = (temp < 4) ? temp : temp + 4;

This could be generalize to write a function random_int_between_excluding(int first, int last, std::vector<int> exclude), though at some point it will be simpler to follow NathanOlivers suggestion and use a std::discrete_distribution instead.

Upvotes: 10

Bathsheba
Bathsheba

Reputation: 234815

If you want to miss out 4 say, then a very good way (which doesn't compromise any statistical properties of the generator), is to draw in the half-open interval [0, 99) then add 1 if the number is 4 or greater.

You do something similar to omit numbers in a range.

This method is a surprisingly good way of modelling the quantile function associated with the desired probability distribution.

Upvotes: 3

Alex Pina
Alex Pina

Reputation: 11

There is an option to do it manually within a reasonable range of numbers..., create a look up table and exclude the numbers that are invalid:

 static int rand_pool[]{1,2,3,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23}; //no number 4
    srand((int)time(0));
        int random_number = rand_pool[rand() % 22];

Upvotes: 0

Related Questions