尝试每次迭代生成一个独特的随机数序列

时间:2016-10-05 14:46:16

标签: c++ random

正如标题所述,我每次运行这个小程序时都试图创建一个独特的随机数序列。

但是,有时我得到的结果如下:

102
201
102

代码

#include <cstdlib>
#include <ctime>
#include <iostream>

using namespace std;

int main() {
        for (int i = 0; i < 3; i++) {
                srand (time(NULL)+i);
                cout << rand() % 3;
                cout << rand() % 3;
                cout << rand() % 3 << '\n' << endl;
        }
}

显然,srand并没有我想要的神奇功能。我希望尽管如此,这是一个合乎逻辑的黑客攻击吗?

Edit1:为了澄清,这只是一个简单的测试程序,用于更大规模的实施。因此,不是rand%3的3次迭代,而是运行1000或更多rand%50。 如果我在其操作的某个时刻看到102,我想要它,以便我再也看不到102.

3 个答案:

答案 0 :(得分:5)

首先,如果您要使用srand / rand,您希望在每次执行程序开始时将其播种一次(并且只播放一次):

int main() {
    srand(time(NULL));
    for (int i = 0; i < 3; i++) {
    cout << rand() % 3;
    cout << rand() % 3;
    cout << rand() % 3 << '\n' << endl;
}

其次,time通常只生成分辨率为1秒的结果,因此即使使用此校正,如果在同一秒内运行程序两次,您可以预期它会在两者中产生相同的结果运行。

第三,你真的不想使用srand / rand<random>中的随机数生成器通常要好得多(也许更重要的是,定义得足够好,它们代表了一个更为人熟知的数量)。

#include <random>
#include <iostream>

int main() { 
    std::mt19937_64 gen { std::random_device()() };
    std::uniform_int_distribution<int> d(0, 2);

    for (int i = 0; i < 3; i++) {
        for (int j=0; j<3; j++)
            std::cout << d(gen);
        std::cout << "\n";
    }
}

然而,基于编辑,这仍然不够。你真正想要的是一个没有重复的随机样本。要做到这一点,你需要做的不仅仅是生成数字。随机生成的数字不仅可以重复,而且如果你生成足够多的数字,将不可避免地重复<(重复),但即使它还不可避免,重复的可能性也会很高。

只要您生成的结果数量与可能结果的数量相比较小,您可以非常轻松地将结果存储在生成它们的集合中,并且仅将结果视为实际输出它以前没有出现在集合中:

#include <random>
#include <iostream>
#include <set>
#include <iomanip>

int main() {
    std::mt19937_64 gen { std::random_device()() };
    std::uniform_int_distribution<int> d(0, 999);
    std::set<int> results;

    for (int i = 0; i < 50;) {
        int result = d(gen);
        if (results.insert(result).second) {
            std::cout << std::setw(5) << result;
            ++i;
            if (i % 10 == 0)
                std::cout << "\n";
        }
    }
}

如果结果数接近可能结果的数量,则效率非常低。例如,让我们假设您的生产数字从1到1000(因此可能有1000个结果)。考虑如果您决定生成1000个结果(即所有可能的结果)会发生什么。在这种情况下,当你生成最后一个结果时,实际上只剩下一种可能性了 - 而不是只产生一种可能性,你会产生一个又一个的随机数,直到你偶然发现剩下的一种可能性。

对于这种情况,有更好的方法来完成这项工作。例如,您可以从容纳所有可能数字的容器开始。要生成输出,请在该容器中生成随机索引。您输出该数字,并从容器中删除该数字,然后重复(但这次,容器是一个较小的容器,因此您将随机索引的范围减少一个)。这样,您生成的每个随机数都会产生一个输出。

可以通过改组数字来实现同样的目的。这有两个缺点。首先,你需要正确地改变它们 - 一个Fischer-Yates shuffle很好地工作,但是否则很容易产生偏见。其次,除非你实际上使用数组中的所有(或非常接近全部)数字,否则这是低效的。

对于极端情况,请考虑使用一些(例如10个)64位数字。在此,首先使用2 64 -1中的数字填充数组。然后你做2 64 -2交换。因此,您只需要执行大约2次 65 操作即可生成10个数字。在这种极端的情况下,问题应该是非常明显的。虽然如果每个产生(比方说)1000个32位的数字并不那么明显,你仍然会有相同的基本问题,只是程度稍低。因此,虽然这是针对少数特定情况执行操作的有效方法,但其适用性相当狭窄。

答案 1 :(得分:0)

生成一个包含27个三位数字的数组,其数字小于3. Shuffle。根据需要迭代洗牌数组,值将是唯一的,直到你用完所有值。

正如其他人所指出的那样,不要继续重新种植随机数生成器。另外,rand是一个糟糕的生成器,你应该使用C ++标准库中更好的选择之一。

答案 2 :(得分:0)

您实际上正在生成一个三位数的基数3。使用您选择的RNG生成0 ... 26范围内的基数为10的数字,并将其转换为基数3.即可得到000 .. 222。

如果你绝对必须避免重复,那么就像pjs建议的那样洗牌。这将导致后来的数字随机性降低。而不是之前的数字,因为它们来自较小的池。