我可以在现代 C++ 中排除随机数范围内的数字或数字子范围吗?

Can I exclude a number or subrange of numbers inside a range of random numbers in modern C++?

我有:

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++ 中实现?

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

 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];

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

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

此方法是对与所需概率分布关联的 分位数 函数建模的一种非常好的方法。

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

Example1: Let's say, I want to generate a random number in between 0 and 100, but this number can never be 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;

Example2: Let's say, I want to generate a random number in between 0 and 100, but this number can never be any number between 4 and 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<int> exclude),但在某些时候遵循 NathanOlivers 的建议并使用 std::discrete_distribution 会更简单。

Example2: Let's say, I want to generate a random number in between 0 and 100, but this number can never be any number between 4 and 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

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

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 为分布中不同值的数量 returns,F - 这些值被过滤掉的数量;然后,如果您需要采样 S 个值,则必须平均采样和过滤 S * N / (N - F) 个值。如果 F 与 N 相比较小,那没关系,但是当 F 接近 N 时效率极低。在您的情况下,N = 100F = 4N / (N - F) = 1.04166...

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