C++有一个函数性病:样本,它从一个范围随机采样并通过输出迭代器放置结果。我的目标是创建一个实用程序,在我们只想从容器中取样单个随机元素的事件中直接返回单个值。
#include <iostream>
#include <random>
#include <algorithm>
#include <iterator>
#include <unordered_set>
#include <array>
template<class InputIt, class URBG>
auto singleRandomSample(InputIt t_begin, InputIt t_end, URBG&& rng)
{
using value_type = typename std::iterator_traits<InputIt>::value_type;
std::array<value_type, 1> out;
std::sample(t_begin, t_end, out.begin(), 1, rng);
return out.at(0);
}
int main()
{
std::default_random_engine rng{ std::random_device{}() };
std::unordered_set<char> vowels{ 'a', 'e', 'i', 'o', 'u', 'y' };
for (std::size_t i = 0; i < 10; ++i)
{
auto randomVowel = singleRandomSample(vowels.begin(), vowels.end(), rng);
std::cout << "Random vowel: " << randomVowel << '\n';
}
}
此函数创建一个临时容器out
,其中添加一个元素,然后按值返回该元素。它使用std::array
,因为我知道我只需要一个元素的空间,尽管如果我决定能够在非默认的可构造类型中使用它很重要,我可以切换到一个std::vector
和一个std::back_inserter
。
直接使用std::sample
作为参数调用rng
是正确的,还是应该使用std::forward(rng)
?
仅仅为了存储一个立即返回的值,整个临时容器就显得有点笨重。还有其他方法可以实现这个功能吗?
发布于 2021-05-20 20:16:00
指针是迭代器。
template <typename InputIterator, typename URBG>
auto singleRandomSample(InputIterator first, InputIterator last, URBG&& rng)
{
using value_type = typename std::iterator_traits<InputIterator>::value_type;
auto result = value_type{};
std::sample(first, last, &result, 1, std::forward<URBG>(rng));
return result;
}
或者,使用更好的C++20接口:
template <std::input_iterator I, std::sentinel_for<I> S, typename URBG>
requires std::uniform_random_bit_generator<std::remove_reference_t<URBG>>
auto singleRandomSample(I first, S last, URBG&& rng)
{
auto result = std::iter_value_t<I>{};
std::ranges::sample(first, last, &result, 1, std::forward<URBG>(rng));
return result;
}
template <std::input_range R, typename URBG>
requires std::uniform_random_bit_generator<std::remove_reference_t<URBG>>
auto singleRandomSample(R&& r, URBG&& rng)
{
auto result = std::ranges::range_value_t<R>{};
std::ranges::sample(r, &result, 1, std::forward<URBG>(rng));
return result;
}
这要求范围的值类型是默认的-可构造的,但是您已经依赖于数组解决方案了。
如果您想要一个不需要默认构造的解决方案,您很可能会使用一个可选的类型,比如std::optional
,或者使用std::variant
和std::monostate
-and,这是一个定制的输出迭代器,可以对其进行嵌入。
发布于 2021-05-20 19:54:50
首先,我会考虑用例。如果需要从单通道序列(从文件或网络中流)进行采样,那么我将保留当前返回所选值的接口。这可能会打开一罐蠕虫。
否则,我将收紧迭代器的要求,以转发迭代器并返回该迭代器。这种接口将不处理非X可构造类型(其中X是一个或多个默认、复制和移动)。
我将把这一职能命名如下:
在对libstdc++和libc++进行校验后,两者似乎都实现了储层采样,这对于第二个接口来说并不难手工实现。对于第一个接口,当前的实现看起来很好(手工编写更容易出错,可读性更低)。
随机访问迭代器情况下的
在已知人口规模的情况下,人们可能会倾向于进行优化,但是由于RNG源不可重入的性质,由于RNG调用次数的不同,这种优化很可能会产生不同的结果。我不会建议实现优化,或者至少我会在文档中把这个奇怪的字体设置为大的、粗体的、红色的字体。
https://codereview.stackexchange.com/questions/260995
复制相似问题