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

pap*_*jam 48 c++ algorithm stl

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

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

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

有没有更好的办法?

Chr*_*ith 46

我在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);
}
Run Code Online (Sandbox Code Playgroud)

样品使用是:

#include <vector>
using namespace std;

vector<int> foo;
/* .... */
int r = *select_randomly(foo.begin(), foo.end());
Run Code Online (Sandbox Code Playgroud)

最后,我采用类似的方法创造了一个更好的设计要点.


Ale*_* C. 32

%这里使用的所有答案都是不正确的,因为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;
}
Run Code Online (Sandbox Code Playgroud)

另一个问题是std::rand假设只有15个随机位,但我们在这里会忘记这一点.

  • 使用std :: uniform_int_distribution可以避免偏见问题. (10认同)
  • 现在,我想知道为什么没有人和我有同样的想法:我的编译器警告我表达式中的整数溢出(RAND_MAX + 1).所以你能解释为什么我们需要+1吗? (5认同)
  • 我是否正确std :: advance将返回void,我刚刚查看http://www.sgi.com/tech/stl/advance.html. (3认同)
  • @math来自`rand`的可能值的*range*为0到RAND_MAX,因此可能值的*count*为RAND_MAX + 1.如果这导致溢出,则需要使用更大的整数类型,或者找到消除偏差的不同方法. (3认同)
  • @AlexandreC。:只有当容器大小接近 RAND_MAX 时,偏差才会变得显着吗?(RAND_MAX 在任何标准库实现上至少为 32767。) (2认同)

Cir*_*四事件 26

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;
}
Run Code Online (Sandbox Code Playgroud)

编译并运行:

g++-7 -o main -std=c++17 -Wall -Wextra -pedantic main.cpp
./main
Run Code Online (Sandbox Code Playgroud)

输出:从无1, 2, 3, 5, 7重复中挑选3个随机数.

为了提高效率,只能O(n)保证ForwardIterator使用的API,但我认为stdlib实现将专门针对O(1)可能的(例如vector).

在GCC 7.2中测试,Ubuntu 17.10.如何在16.04获得GCC 7.

  • 可能有 std::sample_one(),用于*返回*迭代器到一个随机选择的元素。现在我们必须将该单个元素插入到容器中,如果您只需要一个结果,这看起来有点愚蠢。 (3认同)

cpr*_*mer 9

只要RAND_MAX远大于容器大小,这就可以正常工作,否则它会受到Alexandre引用的偏差问题的影响:

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

  • 遭受偏见问题. (5认同)