如何从C ++容器中获取随机元素?

时间:2011-08-04 13:26:49

标签: c++ algorithm stl

从STL范围获取[伪]随机元素的好方法是什么?

我能想到的最好的做法是std::random_shuffle(c.begin(), c.end()),然后从c.begin()中取出我的随机元素。

但是,我可能想要const容器中的随机元素,或者我可能不想要完全洗牌的成本。

有更好的方法吗?

8 个答案:

答案 0 :(得分:45)

我在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 with a better design following a similar approach

答案 1 :(得分:30)

此处使用%的所有答案都不正确,因为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 :(得分:20)

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

输出:从1, 2, 3, 5, 7中挑选3个随机数而不重复。

为了提高效率,只保证O(n),因为ForwardIterator是使用过的API,但我认为stdlib实现会尽可能专注于O(1)(例如vector)。< / p>

在GCC 7.2中测试,Ubuntu 17.10。 How to obtain GCC 7 in 16.04

答案 3 :(得分:9)

只要RAND_MAX远大于容器大小,这样就可以正常工作,否则会遇到偏见问题cited by Alexandre

vector<int>::iterator randIt = myvector.begin();
std::advance(randIt, std::rand() % myvector.size());

答案 4 :(得分:3)

如果您无法访问该尺寸,我认为您会想要执行以下操作。它将迭代器返回到随机元素。

#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;
}

答案 5 :(得分:2)

获取元素数量c.size(),然后在0和random_number之间获得c.size(),并使用:

auto it = c.begin();
std::advance(it, random_number)

查看http://www.cplusplus.com/reference/clibrary/cstdlib/rand/

答案 6 :(得分:1)

您可以尝试获取介于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);

答案 7 :(得分:-1)

您可以使用0~1随机函数为容器中的每个元素生成一个浮点数作为其分数。 然后选择得分最高的那个。