如何设计带有可选随机种子参数的函数以传递给mt19937

时间:2018-10-18 20:01:54

标签: c++ r rcpp

在R中,我可以构建以下roll_die(seed = NULL)函数,该函数返回1到6之间的随机整数,并且允许为RNG指定种子的选项

roll_die_r <- function(seed = NULL){
  # Returns a random number between 1 and 6
  # Optionally specify a RNG seed

  set.seed(seed)
  return(sample(x = 1:6, size = 1L))
}

这很好,因为我可以使用默认的seed = NULL来调用它并获取随机值,也可以使用指定的种子值来调用它,以便获得可重复的结果。

roll_die_r()  # random
roll_die_r(seed = 0)  # always returns 6

如何使用mt19937在c ++中实现相同的功能?我能想到的最好的是

#include <Rcpp.h>
#include <random>
using namespace Rcpp;

// [[Rcpp::plugins(cpp11)]]

// [[Rcpp::export]]
int roll_die_cpp(int seed = -1){
  // Returns a random integer between 1 and 6
  // Optionally specify a RNG seed

  std::mt19937 mt;

  // Seed the RNG
  if(seed == -1) seed = std::random_device{}();
  mt.seed(seed);

  std::uniform_int_distribution<int> dist(1, 6);
  int result = dist(mt);
  return result;
}

但这并不理想,因为用户可能会意外call roll_die_cpp(seed = -1)并期望获得可重现的结果,而不是这种情况。

roll_die_cpp()  # random
roll_die_cpp(seed = 0)  # always returns 5
roll_die_cpp(seed = -1)  # random

我的问题不是专门针对roll_die()方法或随机数生成器,而是有关函数设计的。在R中,我经常使用默认参数设置为NULL的函数,但我不知道如何在c ++中完成相同的工作。

更新: 这是我要得到的另一个例子。

R函数

return_0 <- function(msg = NULL){
  if(!is.null(msg)) print(msg)
  return(0L)
}
return_0()  # doesn't print a message
return_0("hello world")  # prints hello world

cpp功能

// [[Rcpp::export]]
int return_0_cpp(std::string msg = "don't print"){
  if(msg != "don't print") Rcpp::Rcout << msg;
  return(0);
}

return_0_cpp()  # doesn't print a message
return_0_cpp(msg = "hello world")  # prints hello world
return_0_cpp(msg = "don't print")  # doesn't print a message

请注意return_0_cpp()多么尴尬。在cpp中用R创建的干净方法是什么?

2 个答案:

答案 0 :(得分:2)

  

在R中,我经常使用默认参数设置为NULL的函数,但我不知道如何   在c ++中完成同样的事情。

std::optional(自C ++ 17起)用于可选值:

#include <iostream>
#include <optional>

void fun(std::optional<int> v = std::nullopt) {
    if (v) {
        std::cout << "value passed = " << v.value();
    } else {
        std::cout << "no value passed";
    }
}

int main(){ 
    fun();
    fun(4);
}

作为旁注:根据传递的参数数量,使同一个函数执行两项不同的操作时要小心。有人可能会争辩

dice.seed(0); 
auto x = dice.roll();

更明确和可读
auto x = dice.roll(0);

答案 1 :(得分:1)

我经常使用仅标头的库,其工作方式如下(大大简化了):

namespace ran {

inline std::mt19937& generator()
{
    thread_local static std::mt19937 mt{std::random_device{}()};
    return mt;
}

template<typename Integral>
void seed(Integral n)
{
    generator().seed(std::mt19937::result_type(n));
}

template<typename Integral>
Integral number(Integral min, Integral max)
{
    using dist_type = typename std::uniform_int_distribution<Integral>;
    thread_local static dist_type dist;

    return dist(generator(), typename dist_type::param_type(min, max));
}

} // namespace ran

使用thread_local static可确保线程安全并保持性能。它可以重复使用相同的随机数生成器,仅在开始时就播种一次,也可以随时使用特定值重新播种。

int main()
{
    for(auto i = 0; i < 10; ++i)
        std::cout << ran::number(3, 9) << ' ';
    std::cout << '\n';

    ran::seed(5);

    for(auto i = 0; i < 10; ++i)
        std::cout << ran::number(3, 9) << ' ';
    std::cout << '\n';
}