xso::generator — Sampling Methods

Single Samples

We provide methods to extract a single sample from an interval, arbitrary distribution, and a container. Another method also picks a random index given an array size.

Uniform Distributions

template<std::integral T>
1constexpr T sample(T a, T b)

template<std::integral T>
2constexpr T index(T n);

template<std::floating_point T>
3constexpr T sample(T a, T b);

template<std::input_iterator T>
4constexpr auto sample(T b, T e);

template<typename Container>
5constexpr auto sample(const Container &c);
1
Returns a single integer value from a uniform distribution over \([a,b]\). No error checks are performed, and the behaviour is undefined if \(a < b\).
2
Returns a single index from a uniform distribution over \([0,n)\). No error checks are performed, and the behaviour is undefined if \(n=0\).
3
Returns a single real value from a uniform distribution over \([a,b)\). No error checks are performed, and the behaviour is undefined if \(a < b\).
4
Returns a single element from the iteration \([b,e)\), where all elements are equally likely to be returned. No error checks are performed, and the behaviour is undefined if \(e < b\).
5
Returns a single element from container c, where all elements are equally likely to be returned. The method will work on any container c which supports std::cbegin(c) and std::cend(c).

Arbitrary Distributions

We have a C++ [concept][`] that captures a common feature of the distributions defined in the standard {std.random} header: ```cpp template<typename D> concept xso::Distribution = requires { typename D::param_type; }; // <1> ``` 1. All those distributions define an embeddedparam_type` — this is distinct from the standard collections like std::vector.

We can use that concept to overload the sample methods with xso::Distribution arguments as follows:

1constexpr auto sample(Distribution auto &dist);
1
Returns a single variate from the distribution dist e.g. from a std::normal_distribution instance.

NOTE It is convenient to have many sampling methods, all named sample(…), and C++ allows this through overloading. The specific sample version invoked depends on the type of arguments passed. To make this work, we must distinguish between a container like a std::vector and a distribution like a std::normal_distribution. The xso::Distribution concept is a minimal way to identify the distributions defined in the standard library. It relies on all distribution classes having an embedded typename defining a param_type. None of the standard container classes have that.

Examples

Extract single samples from intervals, distributions, and containers:

#include <utilities/utilities.h>
#include <xoshiro.h>
int main()
{
    xso::rng    gen;
    std::size_t n, n_samples = 3;

    std::cout << "Characters from ['a','z']\n";
    for (n = 0; n < n_samples; ++n) std::print("'{:c}' ", gen.sample(std::uint8_t{'a'}, std::uint8_t{'z'}));
    std::print("\n");

    std::cout << "Integers from [1,10]\n";
    for (n = 0; n < n_samples; ++n) std::print("{} ", gen.sample(1, 10));
    std::print("\n");

    std::cout << "Reals from[1,10)\n";
    for (n = 0; n < n_samples; ++n) std::print("{:4.2f} ", gen.sample(1., 10.));
    std::print("\n");

    std::normal_distribution nd{70., 15.};
    std::print("Normals with mean {} and std-dev {}\n", nd.mean(), nd.stddev());
    for (n = 0; n < n_samples; ++n) std::print("{:4.2f} ", gen.sample(nd));
    std::print("\n");

    std::binomial_distribution bd{6, 0.5};
    std::print("Binomials with {} trials and P[success] = {}\n", bd.t(), bd.p());
    for (n = 0; n < n_samples; ++n) std::print("{} ", gen.sample(bd));
    std::print("\n");

    std::array<int, 10> array;
    std::iota(array.begin(), array.end(), 0);
    std::print("Random from the array {}\n", array);
    for (n = 0; n < n_samples; ++n) std::print("{} ", gen.sample(array));
    std::print("\n");
}

Output:

Characters from ['a','z']
'x' 'd' 'n'
Integers from [1,10]
2 10 7
Reals from[1,10)
2.17 3.08 5.18
Normals with mean 70 and std-dev 15
92.30 78.33 66.20
Binomials with 6 trials and P[success] = 0.5
4 1 2
Random from the array [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
2 4 4

Multiple Samples

We provide methods to extract multiple samples without replacement from various sources.

From Containers

We have methods to copy samples from a container without replacement.

template<std::input_iterator Src, typename Dst>
constexpr Dst
1sample(Src b, Src e, Dst dst, std::unsigned_integral auto n);

template<typename Src, typename Dst>
constexpr auto
2sample(const Src &src, Dst dst, std::unsigned_integral auto n);
1
Picks n elements from the sequence \([b,e)\), without replacement and copies the chosen samples into dst. Returns the end of dst.
2
Picks n elements from the container src without replacement and copies the chosen samples into dst. Returns the end of dst.

These methods will preserve the order of the selected elements. You can use the shuffle method to mix those up.
View std::sample for more details.

From Distributions

There is also a method to copy samples from an arbitrary distribution into a destination.

template<typename Dst>
constexpr Dst
1sample(Distribution auto &dist, Dst dst, std::unsigned_integral auto n);
1
Extracts n variates from the distribution dist and puts them into dst. Returns the end of dst.

Examples

Extract multiple samples from a container or a distribution:

#include <utilities/utilities.h>
#include <xoshiro.h>
int main()
{
    xso::rng gen;

    constexpr std::size_t N = 10;
    constexpr std::size_t K = N / 2;

    std::array<int, N> u;
    std::iota(u.begin(), u.end(), 0);
    std::print("Population: {}\n", u);

    std::array<int, K> u_samples;
1    gen.sample(u, u_samples.begin(), u_samples.word_count());
    std::print("Samples:    {}\n", u_samples);

    std::normal_distribution nd{70., 15.};
    std::array<double, N> v;
2    gen.sample(nd, v.begin(), v.word_count());
    std::print("Population: {::4.2f}\n", v);

    std::array<double, K> v_samples;
3    gen.sample(v, v_samples.begin(), v_samples.word_count());
    std::print("Samples:    {::4.2f}\n", v_samples);
}
1
Generate 5 samples from the population of 10 elements.
2
Generate 10 samples from a normal distribution.
3
Generate 5 samples from that array we just generated.

Output:

Population: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1Samples:    [0, 2, 6, 7, 8]
Population: [66.50, 78.99, 70.24, 58.61, 53.95, 48.18, 74.63, 43.87, 82.07, 60.32]
2Samples:    [53.95, 48.18, 43.87, 82.07, 60.32]
1
Note that order is preserved by these samples.
2
That is also true here.

See Also

std::sample
shuffle

Back to top