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

How to design a function with an optional random seed argument to be passed to mt19937

在 R 中,我可以构建以下 roll_die(seed = NULL) 函数,该函数 returns 一个介于 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 中创建的内容的干净方法是什么?

In R I often use functions with default parameters set to NULL but I don't know how to accomplish the same thing in 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);

我经常使用 header 唯一的库,它可以像这样工作(大大简化):

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确保线程安全,同时保持性能。它重复使用相同的随机数生成器,在开始时只为它播种一次,或者您可以随时 re-seed 它使用特定值。

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';
}