使用rand()获取数字,但该数字不能是上次生成的数字

时间:2015-06-23 03:22:50

标签: c++ random std

我想使用std::rand()生成0amountOfNumbers之间的数字,但生成的数字不能与上次生成的数字相同。

我写了这个函数:

void reroll() {
  int newRand;

  while (true) {
    newRand = std::rand() % (amountOfNumbers);
    if (newRand == lastNumber) {
      continue;
    }
    lastNumber = newRand;
    break;
  }

  // do something with newRand
  // ...
}

amountOfNumbers只是一个int(> 1),它定义了上限(例如5,因此可能的数字范围是0到4)。 lastNumber最初-1存储最后生成的数字。

我想知道是否有更好的方式来写这个。

到目前为止,该功能似乎正在运行,但我不确定我的代码是否完美......它看起来有点糟糕。那while (true)让我感到有点不安。

4 个答案:

答案 0 :(得分:3)

代码可以工作,但我会像这样构建它

int reroll(int n, int last) {
  while(true) {
    int v = std::rand() % n;
    if(v!=last)
      return v;
  }
}

void reroll() {
   ...
   int v = reroll(n, last);
   ...
}

此外,您可以通过生成较小范围内的值(少1个)并调整last来完全避免使用while循环。

int reroll(int n, int last) {
  if(last==-1) 
    return std::rand() % n;
  int v = std::rand() % (n-1);
  if (v<last) return v
   return v+1;
}

答案 1 :(得分:2)

我有一些建议。

由于你从未声明lastNumber和amountOfNumbers,我将假设它们是全局的。最好将这些变量作为变量传递给函数。此外,您应该从函数返回新数字,而不是将其设置为void。

以下代码将计算新的代码。我们不会重新滚动,直到有新的滚动,我们将只取一组数字的随机数,但少一个。如果数字大于或等于,我们将重新添加一个,从而避免使用lastNumber。然后该函数返回newRand。这样做可以避免无限循环(尽管很低)的风险,并且它总是在恒定时间内运行。

int reroll(int lastNumber, int amountOfNumbers) 
{
  int newRand;

  newRand = std::rand() % (amountOfNumbers - 1);
  if (newRand >= lastNumber) {
    newRand++;
  }

  return newRand;
}

答案 2 :(得分:0)

while true循环绝对不是一个好习惯,我建议做这样的事情。但是你应该像迈克尔的答案一样使用它,就像这样:

void reroll(int lastNumber, int amountOfNumbers) {
  int newRand = std::rand() % (amountOfNumbers);

  while (newRand == lastNumber) {
    newRand = std::rand() % (amountOfNumbers);
  }

  lastNumber = newRand;
  return newRand;
}

答案 3 :(得分:0)

根据您amountOfNumbers的值,您使用的模数运算可能无法保证均匀分布(这只是您问题的一小部分)。

  • 首先,如果amountOfNumbers大于RAND_MAX,那么您将永远不会看到使用此数字的数字。
  • 接下来,考虑您是否使用此功能为骰子生成0到6之间的值([0,1,2,3,4,5]),RAND_MAX为7.您将看到值01两倍的频率!实际上,RAND_MAX必须至少为32767(无论这意味着什么,根据标准不是很多)......但 不能被6整除,所以当然会有一些具有轻微偏见的值

可以使用模数来缩小范围,但你可能想要在第二个问题中放弃偏差。不幸的是,由于rand的共同实施,将范围扩展到max以后会引入进一步的偏见。

unsigned int rand_range(unsigned int max) {
    double v = max > RAND_MAX ? max : RAND_MAX;
    v /= RAND_MAX;

    int n;
    do {
        n = rand();
    } while (RAND_MAX - n <= RAND_MAX % (unsigned int)(max / v));

    return (unsigned int)(v * n) % max;
}

并不保证不存在任何重复值,但至少会导致重复值的任何偏差显着减少。我们可以使用类似的方法(当前)接受的答案来删除任何重复的值,现在具有最小的偏差:

unsigned int non_repetitive_rand_range(unsigned int max) {
    if (max <= 1) {
        return 0; // Can't avoid repetitive values here!
    }

    static unsigned int previous;
    unsigned int current;
    do {
        current = rand_range(max);
    } while (current != previous);

    previous = current;
    return current;
}

从技术角度来看,这并不一定能保证解决方案的问题。该标准的引用解释了原因:

  

不能保证产生的随机序列的质量,并且已知一些实现产生具有令人沮丧的非随机低阶位的序列。具有特殊要求的应用应使用已知足以满足其需求的发电机。

因此,某些愚蠢,模糊的实现可能会像{I}那样实现rand

int rand(void) {
    return 0;
}

对于这样的实现,没有避免它:rand的这种实现会导致此答案中的代码(以及所有其他当前答案)进入无限循环。您唯一的希望是重新实施randsrand。这是标准中给出的示例实现,如果您必须这样做:

static unsigned long int next = 1;
int rand(void)   // RAND_MAX assumed to be 32767
{
    next = next * 1103515245 + 12345;
    return (unsigned int)(next/65536) % 32768;
}
void srand(unsigned int seed)
{
    next = seed;
}

您可能希望将它们分别重命名为my_randmy_srand,并使用#define rand my_rand#define srand my_srand或使用查找/替换操作。