【问题标题】:In C++, how can I generate a random number that falls between two ranges?在 C++ 中,如何生成介于两个范围之间的随机数?
【发布时间】:2015-11-09 23:09:53
【问题描述】:

如何编写代码来生成介于两个不同范围之间的随机数?

示例:生成一个介于 5 到 7 或 10 到 12 之间的随机数。可能的结果是 5、6、7、10、11 或 12。

【问题讨论】:

  • 只用谷歌搜索“范围内的 C++ 随机数”只需要一小部分时间
  • 有几个选项取决于您想要的分发类型。例如。您可以生成 1 个数字来选择您想要的 2 个范围中的哪一个,然后生成第二个数字来从所选范围中选择一个数字。显然,这可能会扭曲不同尺寸范围的结果。或者:将单独的范围映射到具有“大小”= sum(基本范围大小)的新范围。为新范围生成一个数字,并将结果映射回原始范围之一。 (注意:如果您的任何范围重叠,结果可能再次出现偏差。)
  • 评论者似乎很方便地掩盖了这不是一个单一范围的细节。所有那些反对票... :(
  • 每一个想过这些事情的人都应该好好看看这个专家的精彩演讲:youtube.com/watch?v=LDPMpc-ENqY

标签: c++ random generator


【解决方案1】:

更新下面添加了第二个实现


如果你想让这个通用,你必须编写代码。

想到两个选项:

  • discrete_distribution(只需输入 5、6、7、10、11、12)
  • 生成数字 [0..6) 并索引到数组int arr[]={5,6,7,10,11,12}

第二个:

Live On Coliru

#include <random>
#include <iostream>

int main()
{
    using namespace std;
    vector<int> arr = {5,6,7,10,11,12};

    mt19937 prng { random_device {} () };
    uniform_int_distribution<> dist(0, arr.size()-1);

    int i = 10;
    while (i--)
        std::cout << arr[dist(prng)] << " ";
}

打印

5 5 6 12 11 6 11 12 5 12 

或者,当然类似


更新

通过使用 Boost Interval Container Library 来有效地表示构成域的间隔,可针对许多段或大段进行扩展:

Live On Coliru

template <typename T = int>
struct uniform_draw {

    using set  = boost::icl::interval_set<T>;
    using ival = typename set::interval_type::type;

    uniform_draw(std::initializer_list<ival> data) 
        : set_(make_set(data)), dist_(0, set_.size() - 1)
    { }

    friend std::ostream& operator<<(std::ostream& os, uniform_draw const& ud) {
        return os << ud.set_ << " (#samples:" << ud.set_.size() << ")";
    }

    template <typename Engine>
    T operator()(Engine& engine) {
        uintmax_t index = dist_(engine);

        std::cout << " - index: " << index << " against " << set_ << "\n";

        // I think this can be optimized. I just don't know how to elegantly do that / yet
        for (auto& iv : set_) {
            std::cout << " - index: " << index << " against " << iv << "\n";
            if (index > size(iv)) {
                index -= size(iv);
            } else {
                return iv.lower() + index;
            }
        }

        throw std::range_error("uniform_draw");
    }

private:
    set make_set(std::initializer_list<ival> data) {
        set r;
        for (auto& el : data)
            r.insert(el);
        return r;
    }

    set const set_;
    std::uniform_int_distribution<T> dist_; // TODO make_unsigned<T>?
};

像通常的分发一样使用它:

mt19937 mt { random_device {} () };

uniform_draw<int> dist { {5, 7}, {10, 12} };

std::cout << dist << "\n";

for (int i = 0; i < 10; ++i)
    std::cout << "RESULT: " << dist(mt) << "\n";

打印例如:

{[5,7)[10,12)} (#samples:4)
7 7 6 11 6 6 7 7 7 6 

【讨论】:

  • 怪异的。这与我即将发布的唯一区别是 using namespace std; 向量的名称,我使用了 for 循环。
  • 这个答案需要大数组。如果范围太大怎么办?我确信有一个不需要此类数组的简单解决方案。但是等我写下来就太晚了,所以我放弃了。 :d
  • @A.S.H OP 提出了一个特定的问题,这提供了一个答案。如果他有大而繁琐的数组要处理,那么他应该在问题中这么说。嘿嘿,我不认为你的discrete_distribution 建议是正确的,它需要一系列权重,而不是结果。
  • 干得好,精彩纷呈 :)。好吧,既然结束了,让我澄清一下,我的想法实际上要简单得多。很快,在伪代码中:1)从与两个范围的并集一样宽的范围中均匀地选择 x 2)根据 max(范围 1)检查结果。 3) if
  • 是的,我不怀疑。我只说我们不需要数组。你不必把它带到通用性的极端基础上。但无论如何,这是一个很好的练习。荣誉:)
【解决方案2】:

选择一个随机数 01。如果此数字为0,则在第一个范围内选择一个随机数,否则在第二个范围内选择一个随机数。

如果您希望两个范围内的每个数字具有相等的概率,您可以根据两个范围的大小对第一个决策进行加权。

【讨论】:

  • 如果范围大小不相等,这将不会导致均匀分布。但是OP并没有明确说明,他们只是想要随机,所以这将是随机的..
  • 这与 Jared 之前发布(并删除)的答案相同
  • @A.S.H 我添加了一条关于根据两个范围的大小加权第一选择的评论。
  • 我删除了它,因为根据这个答案它是多余的。此后,OP 对问题进行了很多澄清
【解决方案3】:

我看到了两种方法来做到这一点。假设您的两个范围是 [minA, maxA] 和 [minB, maxB] 其中 maxA

解决方案 1:

1) Generate a random number X in [minA, maxB] first
2) if X falls (maxA,minB) goto 1)
3) At this point X is the output

解决方案 2(更有效,特别是如果两个范围之间的差距很大):

1) rangeA = maxA - minA, rangeB = maxB - minB;
2) generate a random number X within [0,rangeA+rangeB]
3) if X < rangeA then output minA+X else minB+X

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-31
    • 1970-01-01
    相关资源
    最近更新 更多