Home > Software engineering >  std::sample() with integer range
std::sample() with integer range

Time:07-27

How can I pick multiple values randomly in any given integer range?
With std::sample(),
one possible implementation would be:

void Sample(
    int first, int last, std::vector<int> *out, std::size_t n, std::mt19937 *g)
{
    std::vector<int> in{};
    for (int i = first; i < last;   i)
    {
        in.emplace_back(i);
    }

    std::sample(in.begin(), in.end(), std::back_inserter(*out), n, *g);
}

But I'm hesitating because creating in vector seems redundant and impractical,
especially when first is 0 and last is very big number.
Is there any public implementation or library for this requirement? e.g.:

  • sample(int first, int last, ...) or,
  • ranges::sample(int n, ...)

CodePudding user response:

In C 20 you can sample on iota_view

#include <ranges>
#include <vector>
#include <algorithm>
#include <random>

void Sample(
  int first, int last, std::vector<int> *out, std::size_t n, std::mt19937 *g) 
{
  std::ranges::sample(
    std::views::iota(first, last), std::back_inserter(*out), n, *g);  
}

Demo

Note that the above does not work in libstdc because ranges::sample incorrectly uses the std::sample's implementation. Since iota_view's iterator only satisfies Cpp17InputIterator, std::sample requires that the output range must be a random access iterator, which is not the case for back_inserter_iterator.

The workaround is to pre-resize the vector.

void Sample(
  int first, int last, std::vector<int> *out, std::size_t n, std::mt19937 *g) 
{
  out->resize(n);
  std::ranges::sample(
    std::views::iota(first, last), out->begin(), n, *g);  
}

which works in both libstdc and MSVC-STL.

  • Related