Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In C++, how can I generate a random number that falls between two ranges?

How can I write code to generate a random number that falls between two different ranges?

Example: Generate a random number that is between 5 and 7 or between 10 and 12. Possible outcomes are 5, 6, 7, 10, 11, or 12.

like image 208
JZonker Avatar asked Jan 19 '26 15:01

JZonker


1 Answers

UPDATE Second implementation added below


If you want to make this generic, you'll have to code it.

Two options come to mind:

  • discrete_distribution (just feed it 5,6,7,10,11,12)
  • generate numbers [0..6) and index into an array int arr[]={5,6,7,10,11,12}

The second:

Live On Coliru

#include <random>
#include <iostream>

int main()
{
    using namespace std;
    vector<int> arr = {5,6,7,10,11,12};

    mt19937 prng { random_device {} () };
    uniform_int_distribution<> dist(0, arr.size()-1);

    int i = 10;
    while (i--)
        std::cout << arr[dist(prng)] << " ";
}

Prints

5 5 6 12 11 6 11 12 5 12 

Or, similar of course


UPDATE

An alternative implementation that will scale for many segments or large segments, by using Boost Interval Container Library to efficiently represent the intervals that make up the domain:

Live On Coliru

template <typename T = int>
struct uniform_draw {

    using set  = boost::icl::interval_set<T>;
    using ival = typename set::interval_type::type;

    uniform_draw(std::initializer_list<ival> data) 
        : set_(make_set(data)), dist_(0, set_.size() - 1)
    { }

    friend std::ostream& operator<<(std::ostream& os, uniform_draw const& ud) {
        return os << ud.set_ << " (#samples:" << ud.set_.size() << ")";
    }

    template <typename Engine>
    T operator()(Engine& engine) {
        uintmax_t index = dist_(engine);

        std::cout << " - index: " << index << " against " << set_ << "\n";

        // I think this can be optimized. I just don't know how to elegantly do that / yet
        for (auto& iv : set_) {
            std::cout << " - index: " << index << " against " << iv << "\n";
            if (index > size(iv)) {
                index -= size(iv);
            } else {
                return iv.lower() + index;
            }
        }

        throw std::range_error("uniform_draw");
    }

private:
    set make_set(std::initializer_list<ival> data) {
        set r;
        for (auto& el : data)
            r.insert(el);
        return r;
    }

    set const set_;
    std::uniform_int_distribution<T> dist_; // TODO make_unsigned<T>?
};

Use it like your usual distribution:

mt19937 mt { random_device {} () };

uniform_draw<int> dist { {5, 7}, {10, 12} };

std::cout << dist << "\n";

for (int i = 0; i < 10; ++i)
    std::cout << "RESULT: " << dist(mt) << "\n";

Prints e.g.:

{[5,7)[10,12)} (#samples:4)
7 7 6 11 6 6 7 7 7 6 
like image 162
sehe Avatar answered Jan 21 '26 07:01

sehe



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!