【问题标题】:How to generate 7 random numbers, without repeats [closed]如何生成7个随机数,不重复[关闭]
【发布时间】:2019-05-14 18:38:33
【问题描述】:

我想生成 7 个从 0 到 39 的随机数并将它们存储在一维数组中。我必须确保每个数字都不同(例如,不能有两个 7)。

我考虑过随机数,但我只需要 40 个中的 7 个。实际上我必须在学校这样做,我们还没有涉及指针,我们正在使用 rand() 函数来获取随机数。我想解决方案不一定是真正随机的,但至少是随机的。

【问题讨论】:

  • 我想过洗牌这是个好主意,你应该使用它。那么,如果您浪费了 32 个整数的空间怎么办。它简单易行,只需 3 行代码即可完成。
  • 我会随机播放一个包含数字 0..39 的向量(使用 std::shuffle),然后选择前 7 个。
  • 略相关:Rand Considered Harmful。这很长,值得一看,但如果你只对为什么rand 是个坏主意感兴趣,那么演示者会在前 10 分钟左右把它打死。但既然不提出替代方案那有点没用,还有另外 20 分钟。

标签: c++ arrays random


【解决方案1】:

好的,这是我的贡献:

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

int main()
{
    std::vector <int> v (40);
    std::generate (v.begin (), v.end (), [n = 0] () mutable { return n++; });

    std::random_device rd;
    std::mt19937 g (rd ());
    std::shuffle (v.begin (), v.end (), g);

    for (int i = 0; i < 7; ++i)
        std::cout << v [i] << '\n';
}

代表性输出:

5
39
10
17
36
11
31

Live demo

编辑:正如 skeller 所指出的,对 std::generate 的调用可以替换为:

std::iota (v.begin (), v.end (), 0);

这样更整洁。

如果您愿意,也可以将std::vector v (40); 替换为std::array &lt;int, 40&gt; v;

【讨论】:

  • 如果初始集合不是 40 个,而是大得多,那么您认为对一个庞大的集合进行洗牌,只选择 7 个元素是否明智?最好从大集合中选择随机元素。
  • @abelenky 好吧,这不是问题所在。顺便说一句,我没有对你投反对票,所以你(假定)对我的回答投反对票似乎有点小。
  • 不错的答案,可以通过使用 std::iota 而不是使用 lambda 生成来改进 :)
  • @abelenky 对于给定的大小,这并不慢,对于更大的范围,也不会使用您的解决方案,而只是在给定范围内生成一个新的随机值以防发生碰撞。此解决方案适用于给定问题
  • 将改为使用集合(或无序集合)来检测重复项。更多的白痴证明。我们也可以为了好玩而加入布隆过滤器。
【解决方案2】:

我的相同算法的 C++ 版本。

#include <iostream>
#include <algorithm>
#include <random>
#include <vector>
using namespace std;

int main()
{
    std::random_device rd;
    std::mt19937 g (rd ());

    std::vector <int> v(40);
    std::iota(v.begin(), v.end(), 0);  // Fill the vector with values 0-39

    std::vector<int> output(7);
    std::generate(output.begin(), output.end(),
        [v,g]()mutable
        {
            auto pos = v.begin() + (g()%v.size());    // Pick an element at random (by iterator)
            int value = *pos;                         // Get the value of random element
            iter_swap(pos, v.begin() + v.size() - 1); // Swap the random element to the end of vector
            v.pop_back();                             // Erase the last element of vector (the random element)
            return value;                             // Return the randomly chosen value
        });

    for (auto& x :  output) std::cout << x << '\n';

    return 0;
}

【讨论】:

  • 擦除矢量会导致所有以下元素被移动。因此,对于从 n 范围内选择 k 个数字,这具有 O(k * n) 的复杂度,而且在 lambda 中捕获时,您会制作 v 的完整副本。除此之外:扎实的方法
  • 好点。我通过将元素交换到向量的末尾来提高复杂性,然后擦除向量的最后一个元素。
  • 在使用 C++14 或更新版本时,可以通过 [v2 = std::move(v)] 避免捕获中的副本 :)
  • 是否值得一提,简单的 C 版本更短、更快、内存效率更高且更易于理解?
  • 是的,因为你知道如何在 C 中正确地做到这一点。但是下一个外行人可能会尝试更大的数组,超过堆栈大小,将它移动到新的堆中,搞砸删除,.. . 我们都看过并清理过这样的代码。
【解决方案3】:

在 cmets 中指出,教授现代随机方法很重要。所以这是我的看法:

现代 C++ 方法

#include <iostream>
#include <algorithm>
#include <vector>
#include <numeric>
#include <random>
using namespace std;

int main()
{
  std::vector<int> AllValues(40) ; // Vector of 40 integers
  std::iota (std::begin(AllValues), std::end(AllValues), 0); // Fill AllValues with 0..39
  std::random_device RandomDevice; // Create a random device
  std::default_random_engine RandomEngine(RandomDevice()); // Create a random engine, seeding from RandomDevice  
  std::shuffle(std::begin(AllValues), std::end(AllValues), RandomEngine);  // Shuffle AllValues using RandomEngine
  std::vector<int> RandomNumbers(AllValues.begin(), AllValues.begin() + 7); // Create a new vector using the first 7 numbers of AllValues

  // Display the 7 random values
  for (auto i: RandomNumbers)
    std::cout << i << std::endl;  

  return 0;
}

话虽如此,如果我能理解你的感觉,那么你更希望了解它是如何完成的,而不是“正确和现代”的答案。

让我们看看你所要求的参数。

您需要在一个数组中生成 7 个随机数,确保没有一个值出现两次。由于选择两次相同随机数的几率很低(第 7 个数字为 6 / 40 [15%]),我会使用两个循环来完成。这将始终有效,并且在不打高尔夫球的情况下占用的内存最少。

#include <iostream>
#include <random>

using namespace std;

// Those defines could be constants
#define NB_RANDOM_NUMBERS (7)
#define RAND_MODULO (40)

int main()
{
  std::random_device seeder;  // Obtain a seed for the random number engine
  std::mt19937 generator(seeder()); // mersenne_twister_engine seeded with seeder()
  std::uniform_int_distribution<> randomizer(0, RAND_MODULO-1);


  int RandomNumbers[NB_RANDOM_NUMBERS]; // the array that'll get the random numbers

  // A loop for all seven random numbers
  for(int numberIndex = 0; numberIndex < NB_RANDOM_NUMBERS; numberIndex++)
  {
    bool found; // indicates if we found the new number in a previous iteration
    do
    {
      // Generate a new random number
      RandomNumbers[numberIndex] = randomizer(generator);
      /*
      *** If using rand() is required : ***
      RandomNumbers[numberIndex] = rand() % RAND_MODULO;
      */

      // Check if it was found or not in a previous iteration
      found = false;
      for(int checkNumber = 0; checkNumber < numberIndex && !found; checkNumber++)
      {
        // Is the new number already in the array?
        if(RandomNumbers[checkNumber] == RandomNumbers[numberIndex])
        {
          // Exit the loop and restart over
          found = true;
        }
      }
    }
    while(found);

    cout << numberIndex+1 << " : " << RandomNumbers[numberIndex] << std::endl;
  }

  return 0;
}

请注意,NB_RANDOM_NUMBERS-1 越接近 RAND_MODULO,该算法运行的时间就越长。例如,如果您想从 100 个随机数池中生成 90 个随机数,则最后一个随机数将有 89% 的机会已经在 RandomNumbers 数组中。内循环可能会运行很长时间,算法效率不高。

在这种情况下,填充所有值的数组并随机选择它们会更有效:

#include <iostream>
#include <random>

using namespace std;

#define NB_RANDOM_NUMBERS (7)
#define RAND_MODULO (40)

int main()
{
  std::random_device seeder;  // Obtain a seed for the random number engine
  std::mt19937 generator(seeder()); // mersenne_twister_engine seeded with seeder()
  std::uniform_int_distribution<> randomizer(0, RAND_MODULO-1);

  int RandomNumbers[NB_RANDOM_NUMBERS]; // the array that'll get the random numbers
  int AllValues[RAND_MODULO]; // a array of all numeric values to chose from

  for(int i = 0; i < RAND_MODULO; i++)
  {
    AllValues[i] = i;
  }

  // A loop for all seven random numbers
  for(int numberIndex = 0; numberIndex < NB_RANDOM_NUMBERS; numberIndex++)
  {
    int randomIndex;

    // Search for a random index that doesn't contain -1
    do
    {
      randomIndex = randomizer(generator);
    }
    while(AllValues[randomIndex] == -1);

    // Assign the number at the random index you generated
    RandomNumbers[numberIndex] = AllValues[randomIndex];
    // Set it to -1 in preparation of the next iteration
    AllValues[randomIndex] = -1;

    cout << numberIndex+1 << " : " << RandomNumbers[numberIndex] << std::endl;
  }

  return 0;
}

【讨论】:

  • 为了使其更现代,请不要使用#define 并使用 std::array (或向量)而不是 c 样式数组
  • 我只需使用 std::set 填充数字,然后将其元素存储到数组中。这样它是一个单循环。当输出集中所需的元素数量很大时,它也更有效。
  • OP声明数字应该在一维数组中。我假设他们还没有看到向量,或者他们会提到它。此外,OP 没有要求最现代的答案。使用std::setstd::shuffle 可以很容易地实现它,但我怀疑他们仍在学习基本算法,这有点违背了从头开始学习的目的。如果 OP 不喜欢这个答案,我不介意。
  • @skeller 我添加了一种更现代的方法。
  • @MartinVéronneau 最后一个看起来更好:)
【解决方案4】:

这通过从数组中选择随机元素,并缩短数组以删除所选元素来实现。

内嵌评论和解释:

#include <stdio.h>

int main(void) {
    int array[40];
    for(int i=0;i<40; ++i) array[i]=i;  // Fill an array 0-39, in order.

    int output[7];
    for(int i=0; i<7; ++i)
    {
        int rnd_index = rand()%(40-i);    // Pick a random index in the array.
        output[i] = array[rnd_index];     // Copy randomly selected value to the output array
        array[rnd_index] = array[40-i-1]; // Copy the last item in the array to the randomly selected item, effectively removing the selected item from the pool
    }

    // Show the output
    for(int i=0; i<7; ++i) printf("[%d] : %d\n", i, output[i]);
    return 0;
}

输出:

Success #stdin #stdout 0s 9424KB
[0] : 23
[1] : 22
[2] : 3
[3] : 9
[4] : 5
[5] : 10
[6] : 20

注意,使用 rand() 和模运算时会有轻微的选择偏差。 如果您需要更完美的平坦分布,则需要改进。

【讨论】:

  • 为什么使用rand?这些天我们有better 选项。
  • 因为它写的很短,而且很容易理解。 RNG的方法并不重要。
  • 你可以至少播种它(srand)。
  • 问题是“解决方案”将被无知的新手和狂热的程序员复制粘贴。所以它应该反映最佳实践,而不仅仅是狭义的解释。它会被不知道如何修复缺陷的人使用/滥用。因此,如果可能的话,它应该是防弹的(恕我直言)。
  • 这不会像适当的现代选项那样给出随机性。我们应该向新手教授现代 C++,而不是旧的 C 风格。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-09
  • 1970-01-01
  • 1970-01-01
  • 2012-05-28
  • 2014-03-21
相关资源
最近更新 更多