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

Using rand() to get a number but that number can't be the number that was last generated

本文关键字:数字 不能 获取 rand 使用 一个      更新时间:2023-10-16

我想使用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)。最初为CCD_ 6的CCD_。

我想知道是否有更好的方法来写这篇文章。

到目前为止,这个函数似乎可以工作,但我不确定我的代码是否完美。。。看起来有点糟糕。while (true)让我感到有点不安。

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

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

我有一些建议。

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

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

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

虽然true循环肯定不是一个好的练习,但我建议这样做。但你应该把它做成和迈克尔上面的答案一样的结构:

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

根据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;
}

从技术角度来说,这也不一定能保证解决方案出现问题。标准中的这句话解释了为什么:

对于所产生的随机序列的质量没有保证,并且已知一些实现产生具有令人痛苦的非随机低阶比特的序列。具有特定要求的应用程序应该使用已知足以满足其需求的生成器。

因此,一些愚蠢、晦涩的实现可能会实现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或使用查找/替换操作。