JZonker
JZonker

Reputation: 81

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.

Upvotes: 2

Views: 252

Answers (3)

I see two ways to do this. Let's say your two ranges are [minA, maxA] and [minB, maxB] where maxA < minB (if not just swap the two ranges)

Solution 1:

1) Generate a random number X in [minA, maxB] first
2) if X falls (maxA,minB) goto 1)
3) At this point X is the output

Solution 2 (more efficient especially if the gap between the two ranges is big):

1) rangeA = maxA - minA, rangeB = maxB - minB;
2) generate a random number X within [0,rangeA+rangeB]
3) if X < rangeA then output minA+X else minB+X

Upvotes: 0

Barmar
Barmar

Reputation: 780769

Pick a random number 0 or 1. If this number is 0, pick a random number in the first range, otherwise pick a random number in the second range.

If you want each number in the two ranges to have equal probability, you could weight the first decision based on the sizes of the two ranges.

Upvotes: 0

sehe
sehe

Reputation: 392903

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 

Upvotes: 10

Related Questions