STL範囲から[疑似]ランダム要素を取得する良い方法は何ですか?
考えられる最善の方法は、std::random_shuffle(c.begin(), c.end())
を実行し、c.begin()
からランダム要素を取得することです。
ただし、const
コンテナーからランダムな要素が必要な場合や、フルシャッフルのコストが不要な場合があります。
もっと良い方法はありますか?
ここで_%
_を使用するすべての答えは正しくありません。なぜならRand() % n
は偏った結果を生成するからです:_Rand_MAX == 5
_を想像してください。 1と数字2または3よりも。
これを行う正しい方法は次のとおりです。
_template <typename I>
I random_element(I begin, I end)
{
const unsigned long n = std::distance(begin, end);
const unsigned long divisor = (Rand_MAX + 1) / n;
unsigned long k;
do { k = std::Rand() / divisor; } while (k >= n);
std::advance(begin, k);
return begin;
}
_
もう1つの問題は、_std::Rand
_が15ビットのランダムビットしか持っていないことを前提としていることですが、ここでは忘れます。
このソリューションを他の誰かが参照したGoogle+の記事に投稿しました。ここに投稿します。これは、std :: uniform_int_distributionを使用してバイアスを回避するため、他のものよりわずかに優れているためです。
#include <random>
#include <iterator>
template<typename Iter, typename RandomGenerator>
Iter select_randomly(Iter start, Iter end, RandomGenerator& g) {
std::uniform_int_distribution<> dis(0, std::distance(start, end) - 1);
std::advance(start, dis(g));
return start;
}
template<typename Iter>
Iter select_randomly(Iter start, Iter end) {
static std::random_device rd;
static std::mt19937 gen(rd());
return select_randomly(start, end, gen);
}
サンプルの使用は次のとおりです。
#include <vector>
using namespace std;
vector<int> foo;
/* .... */
int r = *select_randomly(foo.begin(), foo.end());
私は最終的に 同様のアプローチに従ってより良いデザインの要点 を作成しました。
C++ 17 _std::sample
_
これは、繰り返しなく複数のランダム要素を取得する便利な方法です。
main.cpp
_#include <algorithm>
#include <iostream>
#include <random>
#include <vector>
int main() {
const std::vector<int> in{1, 2, 3, 5, 7};
std::vector<int> out;
size_t nelems = 3;
std::sample(
in.begin(),
in.end(),
std::back_inserter(out),
nelems,
std::mt19937{std::random_device{}()}
);
for (auto i : out)
std::cout << i << std::endl;
}
_
コンパイルして実行:
_g++-7 -o main -std=c++17 -Wall -Wextra -pedantic main.cpp
./main
_
出力:3つの乱数が_1, 2, 3, 5, 7
_から繰り返し選択されます。
効率のために、ForwardIterator
が使用されるAPIであるため、O(n)
のみが保証されますが、stdlib実装は可能な場合O(1)
に特化すると思います(例:vector
)。
GCC 7.2、Ubuntu 17.10。でテスト済み 16.04でGCC 7を取得する方法 。
サイズにアクセスできない場合は、次のことをしたいと思います。ランダム要素の反復子を返します。
#include <algorithm>
#include <iterator>
template <class InputIterator> InputIterator
random_n(InputIterator first, InputIterator last) {
typename std::iterator_traits<InputIterator>::difference_type distance =
std::distance(first, last);
InputIterator result = first;
if (distance > 1) {
// Uses std::Rand() naively. Should replace with more uniform solution.
std::advance( result, std::Rand() % distance );
}
return result;
}
// Added in case you want to specify the RNG. RNG uses same
// definition as std::random_shuffle
template <class InputIterator, class RandomGenerator> InputIterator
random_n(InputIterator first, InputIterator last, RandomGenerator& Rand) {
typename std::iterator_traits<InputIterator>::difference_type distance =
std::distance(first, last);
InputIterator result = first;
if (distance > 1) {
std::advance( result, Rand(distance) );
}
return result;
}
要素の数c.size()
を取得し、0からc.size()
の間のrandom_number
を取得し、以下を使用します。
auto it = c.begin();
std::advance(it, random_number)
http://www.cplusplus.com/reference/clibrary/cstdlib/Rand/ をご覧ください
0からコンテナの要素数の間の乱数を取得しようとすることができます。その後、コンテナの対応する要素にアクセスできます。たとえば、次のことができます。
#include <cstdlib>
#include <ctime>
// ...
std::srand(std::time(0)); // must be called once at the start of the program
int r = std::Rand() % c.size() + 1;
container_type::iterator it = c.begin();
std::advance(it, r);