0CCh Blog

使用std::sample获取随机样本

C++17标准库提供了一个std::sample函数模板用于获取随机样本,该样本是输入全体样本的一个子集。具体例子如下:

#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>
#include <random>
int main()
{
std::vector<int> data;
for (int i = 0; i < 10000; ++i) {
data.push_back(i);
}

std::sample(data.begin(),
data.end(),
std::ostream_iterator<int>{std::cout, "\n"},
10,
std::default_random_engine{});
}

可以看到std::sample需要5个参数,其中前2个参数是全体样本的合计的beginend迭代器,它定义了全体样本的范围。第3个参数则是输出迭代器,第4个参数是需要样本的数量,最后是随机数引擎。注意这里std::default_random_engine没有设置seed,这必然导致每次运行获取的样本相同。

以上代码的输出结果为:

0
488
963
1994
2540
2709
2835
3518
5172
7996

我们可以为随机数引擎设置seed

std::random_device rd;
std::default_random_engine eng{ rd() };
std::sample(data.begin(),
data.end(),
std::ostream_iterator<int>{std::cout, "\n"},
10,
eng);

这样每次样本就会发生变化。另外std::sample是有返回值的,返回的是最后一个随机样本之后的迭代器。它的作用是确定随机样本在输出容器中的范围,例如:

#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>
#include <random>
int main()
{
std::vector<int> data;
for (int i = 0; i < 10000; ++i) {
data.push_back(i);
}

std::vector<int> out_data;
out_data.resize(100);
std::random_device rd;
std::default_random_engine eng{ rd() };
auto end = std::sample(data.begin(),
data.end(),
out_data.begin(),
10,
eng);

std::cout << "coll size: "
<< out_data.size()
<< std::endl;
for (auto it = out_data.begin(); it != end; ++it) {
std::cout << *it << std::endl;
}
}

以上代码的输出结果为:

coll size: 100
1708
1830
2803
3708
5146
7376
7867
8059
8271
9448

可以看到,虽然容器的大小是100,但是我们只填充10个随机样本。最后需要说明一下std::sample对于两个迭代器参数的要求,首先源迭代器至少是一个输入迭代器的时候,目标迭代器至少可以是一个输出迭代器。但是当源迭代器不是一个向前迭代器,那么目标迭代器必须是一个随机迭代器。这一点很好理解,当源迭代器不能确保随机的情况下,只能将目的迭代器随机以确保样本的随机性。