【问题标题】:Using rand() to get a number but that number can't be the number that was last generated使用 rand() 获取一个数字,但该数字不能是最后生成的数字
【发布时间】:2015-06-23 03:22:50
【问题描述】:

我想使用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)让我有点不安。

【问题讨论】:

  • 你能详细说明你为什么要这样做(有效地去随机化你的随机数)吗?
  • 有更好的random enginesstd::rand() 可用,可能没有这些问题。
  • 您是否想生成连续的数字,然后改为random_shuffle
  • 那么,第三个数字和第一个数字匹配可以吗?
  • @EdHeal “请理解随机。”哦,我愿意。只要不是连续两次生成相同的数字,随机生成的数字就可以了。

标签: c++ random std


【解决方案1】:

代码有效,但我会像这样构造它

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

【讨论】:

    【解决方案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;
    }
    

    【讨论】:

    • 我喜欢你的无环解决方案!
    【解决方案3】:

    while true 循环绝对不是好习惯,我建议做这样的事情。但是您应该使用与上面迈克尔的答案相同的结构,如下所示:

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

    【讨论】:

    • 我的新代码是基于你的。 int newRand = std::rand() % (amountOfNumbers); while (newRand == lastNumber) { newRand = std::rand() % (amountOfNumbers); } lastNumber = newRand;谢谢
    【解决方案4】:

    根据您对 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 或使用查找/替换操作。

    【讨论】:

    • 你的while (RAND_MAX - n &lt;= RAND_MAX % (unsigned int)(v * max)); 不是一个好主意......说max 高于RAND_MAX:你的循环将旋转直到你侥幸将n 值设为0,这对于常见的现代实现其中RAND_MAX 是 2147483647 是一个非常非常长的投篮,相应的长旋转。
    • 对于amountOfNumbers&lt; RAND_MAX,任何想要消除偏见的人都可以在n = rand()n &gt;= RAND_MAX / amountOfNumbers * amountOfNumbers之后旋转。
    • @TonyD 哎呀!这表明即使是花了很多时间思考这个问题的人也很容易弄错!现在怎么样了?
    • “现在看起来怎么样?” - 老实说,仍然令人困惑:我不明白你为什么使用double 等让事情变得如此复杂。 . 但是,通过它:你的前两行等于double v = std::max(max / RAND_MAX, 1.0);,然后RAND_MAX % (unsigned)(max / v) 简化为(当v == 1.0RAND_MAX % max,或(当v == max / RAND_MAXRAND_MAX % (unsigned)(max / (max / RAND_MAX))RAND_MAX % (unsigned)(RAND_MAX),这是0。除非我在那里搞砸了,不管&lt;= 的意图如何,0 可能是 r.h.s.看起来不太好。
    • @TonyD 您是否考虑过其中许多操作数是浮点操作数?是的,例如,当RAND_MAX * 2max 给出时,右侧应该为0,因为不会有任何偏差可以丢弃。如果您认为有问题,请编写一个测试用例(以rand() 的形式产生导致错误的值,并调用相关函数)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-08-18
    • 2023-03-09
    • 1970-01-01
    • 1970-01-01
    • 2014-05-09
    • 2018-06-06
    • 2013-06-13
    相关资源
    最近更新 更多