【问题标题】:Can I exclude a number or subrange of numbers inside a range of random numbers in modern C++?我可以在现代 C++ 中的随机数范围内排除一个数字或数字子范围吗?
【发布时间】:2021-12-02 20:46:12
【问题描述】:

我有:

std::random_device rd;
std::mt19937 mt(rd());
std::uniform_int_distribution<int> probability(0, 100);

我想排除这个概率范围内的一些数字。
示例 1:假设我想生成一个介于 0 和 100 之间的随机数,但这个数字永远不会是 4。
示例 2:假设我想生成一个 0 到 100 之间的随机数,但这个数字永远不能是 4 到 7 之间的任何数字。

我想知道是否可以在不使用std::rand 的情况下在现代C++ 中实现?

【问题讨论】:

标签: c++ c++11 random c++17


【解决方案1】:

如果您想继续使用uniform_int_distribution,您可以像这样手动操作:

示例 1:假设我想生成一个介于 0 到 100 之间的随机数,但这个数字永远不会是 4。

std::random_device rd;
std::mt19937 mt(rd());
std::uniform_int_distribution<int> distribution(0,99);
auto temp = distribution(mt);
auto random_number = (temp < 4) ? temp : temp + 1;

示例 2:假设我想生成一个介于 0 到 100 之间的随机数,但这个数字永远不能是 4 到 7 之间的任何数字。

std::random_device rd;
std::mt19937 mt(rd());
std::uniform_int_distribution<int> distribution(0,96);
auto temp = distribution(mt);
auto random_number = (temp < 4) ? temp : temp + 4;

这可以概括为编写一个函数 random_int_between_excluding(int first, int last, std::vector&lt;int&gt; exclude),尽管在某些时候遵循 NathanOlivers 的建议并改用 std::discrete_distribution 会更简单。

【讨论】:

  • 花时间写出解决方案做得很好(不像我,不想面对解开“4到7之间”的复杂性)。投赞成票! (我个人认为probability 用词不当,但这是一个小问题。)
  • 这是一种非常巧妙的方法,但是int 没有环绕行为,因此这里存在未定义行为的小风险。您应该改为在 unsigned int 上进行增量
【解决方案2】:

Example2:比方说,我想生成一个介于 0 之间的随机数 和 100,但这个数字不能是 4 到 7 之间的任何数字。

这就是std::piecewise_constant_distribution 的用途。

    std::vector<int> i{0,  4, 8, 101};
    std::vector<int> w{ 4,  0,  93};
    std::piecewise_constant_distribution<> d(i.begin(), i.end(), w.begin());

Live demo

【讨论】:

  • 请注意,这是一个连续(浮点)分布。您需要 101.0 上限,以便范围 100.0, 101.0 可以向下舍入到 100。
【解决方案3】:

如果你想错过 4 说,那么一个很好的方法(不损害生成器的任何统计属性)是在半开区间 [0, 99) 中绘制,然后添加 1 如果数字为 4 或更大。

您执行类似于省略范围内的数字的操作。

这种方法是对与所需概率分布相关的分位数函数进行建模的好方法。

【讨论】:

    【解决方案4】:

    您可以在均匀分布上使用任意复杂度的过滤器:

    template<typename D, typename G, typename F>
    auto sample(D &distribution, G &generator, F const &filter)
    {
        while(true)
        {
            auto const value = distribution(generator);
            if(filter(value))
                return value;
        }
    }
    

    您的示例案例转换为以下

    std::random_device rd;
    std::mt19937 mt(rd());
    std::uniform_int_distribution<int> probability(0, 100);
    
    auto const filter = +[](int n) {return n < 4 || n > 7;}
    int const i = sample(probability, mt, filter);
    

    您必须记住,这种过滤是有代价的。

    N 是分布返回的不同值的数量,F - 过滤掉的这些值的数量;然后,如果您需要对S 值进行采样,则必须对S * N / (N - F) 值进行平均采样和过滤。如果 F 与 N 相比较小,那没关系,但当 F 接近 N 时,效率非常低。在您的情况下,N = 100F = 4N / (N - F) = 1.04166...

    如果您更喜欢可读性和简单性,那是您的选择。否则,如果您需要性能,您最好尝试分段分布或手动混淆值范围。

    【讨论】:

      【解决方案5】:

      有一个选项可以在合理的数字范围内手动执行...,创建一个查找表并排除无效的数字:

       static int rand_pool[]{1,2,3,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23}; //no number 4
          srand((int)time(0));
              int random_number = rand_pool[rand() % 22];
      

      【讨论】:

      • 还有不合理的数字范围。您可以通过编程方式生成此表。
      • @pptaszni:使用元编程/constexpr 编程。
      • 这是一个巨大的退步,因为rand() % 22 实际上并不统一
      • 假设RAND_MAX % 22 是例如11 那么前半部分的概率高于后半部分
      • @463035818_is_not_a_number - 该特定数组中有 22 个值,而 rand()% 22 应该在这 22 个值中生成一个数字。 %23 将生成编译器警告。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-16
      • 2021-12-23
      • 2018-04-08
      • 2017-03-31
      相关资源
      最近更新 更多