[英]Using c++11's <random> header, what is the correct way to get an integer between 0 and n?
我剛剛開始首次使用C ++ 11的<random>
標題,但仍有一些東西看起來有些神秘。 這個問題是關於完成一項非常簡單的任務的預期的,慣用的,最佳實踐的方法。
目前,在我的代碼的一部分我有這樣的事情:
std::default_random_engine eng {std::random_device{}()};
std::uniform_int_distribution<> random_up_to_A {0, A};
std::uniform_int_distribution<> random_up_to_B {0, B};
std::uniform_int_distribution<> random_up_to_some_other_constant {0, some_other_constant};
然后當我想要一個介於0和BI之間的整數時,調用random_up_to_B(eng)
。
由於這開始看起來有點傻,我想實現一個函數rnd
,使得rnd(n, eng)
返回0到n之間的隨機整數。
像下面這樣的東西應該工作
template <class URNG>
int rnd(int n, URNG &eng) {
std::uniform_int_distribution<> dist {0, n};
return dist(eng);
}
但這涉及每次創建一個新的分發對象,我得到的印象不是你應該這樣做的方式。
所以我的問題是,使用<random>
標題提供的抽象,完成這個簡單任務的預期最佳實踐方法是什么? 我問,因為我一定要做比以后更復雜的事情,我想確保我以正確的方式使用這個系統。
uniform_int_distribution
構造成本不應太高,因此每次使用新的限制創建一個應該沒問題。 但是,有一種方法可以使用具有新限制的相同對象,但這很麻煩。
uniform_int_distribution::operator()
有一個重載,它接受一個uniform_int_distribution::param_type
對象,該對象可以指定要使用的新限制,但是param_type
本身是一個opaque類型,除了從現有的uniform_int_distribution
提取它之外,沒有可移植的方法來構造它。實例。 例如,以下函數可用於構造uniform_int_distribution::param_type
。
std::uniform_int_distribution<>::param_type
make_param_type(int min, int max)
{
return std::uniform_int_distribution<>(min, max).param();
}
將這些傳遞給operator()
,生成的結果將在指定的范圍內。
因此,如果您真的想重用相同的uniform_int_distribution
, param_type
使用上面的函數創建並保存param_type
多個實例,並在調用operator()
時使用它們。
上面的答案是不准確的,因為標准確實指定param_type
可以使用與相應分布類型的構造函數使用的相同的分布參數構造。 感謝@TC 指出這一點 。
來自§26.5.1.6/ 9 [rand.req.dist]
對於
D
參數的每個構造函數,該參數對應於分布的參數,P
應具有相應的構造函數,這些構造函數具有相同的要求並且在數量,類型和默認值方面具有相同的參數。...
所以我們不需要不必要地構造分布對象來提取param_type
。 而是可以將make_param_type
函數修改為
template <typename Distribution, typename... Args>
typename Distribution::param_type make_param_type(Args&&... args)
{
return typename Distribution::param_type(std::forward<Args>(args)...);
}
可以用作
make_param_type<std::uniform_int_distribution<>>(0, 10)
回答我自己的問題:通過調整本文檔中的示例,以下似乎是實現函數返回0到n-1之間的隨機整數的正確方法:
template<class URNG>
int rnd(int n, URNG &engine) {
using dist_t = std::uniform_int_distribution<>;
using param_t = dist_t::param_type;
static dist_t dist;
param_t params{0,n-1};
return dist(engine, params);
}
為了使其成為線程安全的,必須避免static
聲明。 一種可能性就是沿着這些線創建一個便利類,這就是我在我自己的代碼中使用的:
template<class URNG>
class Random {
public:
Random(): engine(std::random_device{}()) {}
Random(typename std::result_of<URNG()>::type seed): engine(seed) {}
int integer(int n) {
std::uniform_int_distribution<>::param_type params {0, n-1};
return int_dist(engine, params);
}
private:
URNG engine;
std::uniform_int_distribution<> int_dist;
};
這是用(例如) Random<std::default_random_engine> rnd
實例化的,然后可以用rnd.integer(n)
獲得隨機整數。 從其他分布中采樣的方法可以很容易地添加到這個類中。
為了重復我在評論中所說的內容,重復使用分布對象對於統一采樣整數的特定任務可能是不必要的,但對於其他分布,我認為這比每次創建它更有效,因為有一些采樣算法一些可以通過同時生成多個值來節省CPU周期的發行版。 (原則上,甚至uniform_int_distribution
也可以通過SIMD矢量化來實現。)如果你不能通過保留分發對象來提高效率,那么很難想象為什么他們會以這種方式設計API。
Hooray for C ++及其不必要的復雜性! 這結束了下午的工作完成了一個簡單的五分鍾任務,但至少我對我現在正在做的事情有了更好的了解。
根據不同參數生成代碼的慣用方法是根據需要創建分布對象,每個均勻的uniform_int_distribution范圍 :
std::random_device rd;
std::default_random_engine eng{rd()};
int n = std::uniform_int_distribution<>{0, A}(eng);
如果您擔心未能完全利用分發的內部狀態可能會妨礙性能,您可以使用單個分發並每次傳遞不同的參數:
std::random_device rd;
std::default_random_engine eng{rd()};
std::uniform_int_distribution<> dist;
int n = dist(eng, decltype(dist)::param_type{0, A});
如果這看起來很復雜,請考慮到大多數情況下,您將根據具有相同參數的相同分布生成隨機數(因此分布構造函數采用參數); 通過改變參數,您已進入高級領域。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.