生成小范围的不同随机数?

时间:2016-01-16 20:04:22

标签: c++ algorithm

我正在尝试生成一小段(约20个)不同/唯一的随机数。

以下是我现在所拥有的:

unique_random.h

#ifndef UNIQUE_RANDOM_H
#define UNIQUE_RANDOM_H

// generates a pseudo-random number in [min, max]
int random_number (unsigned int min, unsigned int max) {
    static bool seed_initialized = false;

    if (!seed_initialized) {
        seed_initialized = true;
        srand((unsigned int) time(NULL));
    }

    return rand() % (max - min + 1) + min; 
} 

// generates a random number different from the previously generated
int random_number_without_these (int min, int max, std::set<int>& generated) {
    int res = random_number (min, max);

    // if res one of the previous, generate again
    while (s.find(res) != s.end()) {
        res = random_number (min, max);
    }

    return res;
}

#endif

然后将调用上述函数:

main.cpp

#include <iostream>
#include <time.h>
#include <set>

#include "unique_random.h" 

int main() {

    std::set<int> already_generated;

    for (auto i = 0; i < 20; ++i) {

        int rand =  random_number_without_these(1,20, already_generated);
        already_generated.insert(rand);
    }

}

其中预期结果是已生成20个连续唯一值。我现在写的内容需要两个函数,random_number_without_these()random_number()和一个容器set<int>,以便工作,这就是我想知道的原因:

是否有更简单的方法可以生成短距离的独特随机数,可能与现有代码一致?

4 个答案:

答案 0 :(得分:5)

使用std::setstd::uniform_int_distribution这实际上非常简单:

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

std::set<int> generate_numbers(const int min, const int max, const int count)
{
    std::set<int> numbers;
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis(min, max);

    while (numbers.size() < count)
    {
        numbers.insert(dis(gen));
    }

    return numbers;
}

int main()
{
    auto numbers = generate_numbers(1, 20, 20);
    for (auto const v : numbers)
    {
        std::cout << v << ' ';
    }
    std::cout << '\n';
}

我只是没有看到使用std::set的意义,因为这会保持所有值的排序,你可以使用一个简单的循环来生成数字,或std::iota。使用std::unordered_set我可以看到这一点。

答案 1 :(得分:3)

我想建议这个替代功能。它基本上是原始SGI标准模板库中的random_sample_n算法。它以均匀的概率按顺序产生数字。

#include <algorithm>
#include <iostream>
#include <vector>

int random_number(int N) // random value in [0, N)
{
    static std::random_device seed;
    static std::mt19937 eng(seed());
    std::uniform_int_distribution<> dist(0, N - 1);
    return dist(eng);
}

std::vector<int> random_sample(int first, int last, int n)
{
    std::vector<int> numbers;
    int remaining = last - first + 1;
    int m = std::min(n, remaining);
    while (m > 0) {
        if (random_number(remaining) < m) {
            numbers.push_back(first);
            --m;
        }
        --remaining;
        ++first;
    }
    return numbers;
}

int main()
{
    auto numbers = random_sample(1, 100, 20);
    for (int value : numbers) {
        std::cout << value << " ";
    }
    std::cout << '\n';
}

Live demo on ideone.com

答案 2 :(得分:2)

至于记录,下面是Fisher-Yates-Knuth-Durstenfeld的忠实实施,并没有创建源集的明确表示。

FYKD的一般思想是从序列中均匀地采样。将所选元素放在序列的末尾,并继续对序列减去最后一个元素进行采样。冲洗并重复,直到你有足够的数字。

可以为具有可索引生成器的序列模拟此行为,而无需在内存中创建序列。

std::set<long>
rand_unique(const unsigned n, const long max)
{
    std::set<long> selected;

    if (max < 0l) {
        throw std::range_error("max must be >= 0");
    }

    if (n > max) {
        throw std::range_error("too many random numbers requested");
    }

    while (selected.size() < n) {
        long rnd = random() % (max - (long)selected.size());
        if (selected.empty()) {
            /* first number, may even be outside the loop */
            selected.insert(rnd);
        } else {        
            auto it = selected.lower_bound(rnd);
            /* how many numbers are before this one? */
            const long delta = (long)std::distance(selected.begin(), it);
            /**
             * all numbers before the current one have been struck,
             * so shift accordingly by delta
             **/
            rnd += delta;
            /* still a collision? skip over those until a miss */
            while (selected.find(rnd) != selected.end()) {
                rnd += 1l;
            }
            assert(rnd >= 0);
            assert(rnd < max);
            selected.insert(rnd);
        }
    }

    return selected;
}

这个功能很好,即使使用n == maxO(mn))也会终止,即rand_unique(1000, 1000)实际上无需蒙特卡罗指数时间。

由于这有点难以做到,所以请随时指出任何遗留问题。

答案 3 :(得分:1)

当然。如果#include <iostream> #include <string> #include <vector> std::vector<int> GenUnique(int min, int max, int count) { std::vector<int> numbers(max - min + 1), result(count); for (int i = 0; i < max - min + 1; ++i) { numbers[i] = i + min; } for (int i = 0; i < count; ++i) { int next_index = rand() % (numbers.size()); result[i] = numbers[next_index]; numbers[next_index] = numbers.back(); numbers.pop_back(); } return result; } int main() { const auto seq = GenUnique(1, 20, 20); for (int elem : seq) { std::cout << elem << " "; } std::cout << std::endl; } 小到:

4 16 10 1 2 11 15 20 18 19 3 5 12 9 6 14 17 13 8 7 

可能的输出:

{{1}}