Каков хороший способ получить [псевдо-] случайный элемент из диапазона STL?
Лучшее, что я могу придумать, - это сделать std::random_shuffle(c.begin(), c.end())
, а затем взять мой случайный элемент из c.begin()
.
Однако мне может понадобиться случайный элемент из контейнера const
, или мне может не потребоваться стоимость полного перетасовки.
Есть ли лучший способ?
Ответ 1
Все ответы с использованием %
здесь неверны, так как rand() % n
приведет к смещенным результатам: представьте RAND_MAX == 5
, а количество элементов равно 4. Тогда вы получите в два раза больше чисел 0 и 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;
}
Другая проблема заключается в том, что std::rand
предполагается, что имеет только 15 случайных бит, но мы забудем об этом здесь.
Ответ 2
Я разместил это решение в статье в 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());
В итоге я создал gist с лучшим дизайном, следуя аналогичному подходу.
Ответ 3
С++ 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
без повторений.
Для эффективности гарантируется только O(n)
, поскольку ForwardIterator
является используемым API, но я думаю, что реализации stdlib будут специализироваться на O(1)
, где это возможно (например, vector
).
Протестировано в GCC 7.2, Ubuntu 17.10. Как получить GCC 7 в 16.04.
Ответ 4
Это прекрасно работает, пока RAND_MAX намного больше, чем размер контейнера, в противном случае он страдает от проблемы смещения, процитированной Александром:
vector<int>::iterator randIt = myvector.begin();
std::advance(randIt, std::rand() % myvector.size());
Ответ 5
Если вы не можете получить доступ к размеру, я думаю, вы хотели бы сделать следующее. Он возвращает итератор в случайный элемент.
#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;
}
Ответ 6
Возьмите количество элементов c.size()
, затем получите random_number
между 0 и c.size()
и используйте:
auto it = c.begin();
std::advance(it, random_number)
Посмотрите http://www.cplusplus.com/reference/clibrary/cstdlib/rand/
Ответ 7
Вы можете попытаться получить случайное число между 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);
Ответ 8
Вы можете использовать 0 ~ 1 случайную функцию, чтобы генерировать число с плавающей точкой для каждого элемента в контейнере как его счет.
И затем выберите тот, который имеет наивысший балл.