【问题标题】:Suggestion about unique values generation关于唯一值生成的建议
【发布时间】:2016-12-22 11:49:07
【问题描述】:

我想生成一系列“随机”唯一数字,用于纸牌游戏!这些数字应该在 0 到 81 之间。 在这个阶段我不关心安全性或速度,我只想要一些简单的东西来完成工作。

在下面的代码中,我设法在包含它们的数组中创建了 2 个唯一随机数,但其余 10 个数字不会改变,而是保持 -1,即初始值。 我找到了更准确的随机数生成方法,但我会在以后检查它们!

#include <stdio.h>
#include <stdlib.h>




int getRandomNumber(int Min, int Max)
{
    double rnd= (double)rand()/((double)RAND_MAX+1);
    return   (int)(rnd*(Max-Min+1))+Min;
}
int main()
{
    int j,counter,temp,deck[13];
    srand(time(NULL));
    int i;
    counter=1;
    for (i=0;i<12;i++)
        {deck[i]=-1;
        temp = getRandomNumber(0,81);

        for (j=0;j<=i;j++)
            {if (temp==deck[j])
                {counter=0;}
            if (counter!=0)
                deck[i]=temp;
            }
        }
    for(i=0;i<12;i++)
        printf("%d ",deck[i]);

}

【问题讨论】:

  • 这里有很多关于选择唯一号码的问题。一种方法是从可用数字数组开始,随机选择 数组索引,然后从数组中删除该元素。这里是one such answer
  • 如果您已经知道所需数字集的域 (0..81),为什么不直接加载这 82 个值的序列,然后对它们执行简单的Fisher-Yates shuffle
  • 问题说数字介于 0 和 81 之间。 1...80。您可以使用以下语句:value = (rand() %80) +1;,如果您不想要任何重复的数字,那么对于每个新的“值”,在将新的“值”插入数组之前搜索数组以确保它不在数组中。
  • 好吧,我需要使用 rand 来创建这些值!有值=(rand()%80) +1;我没有得到重复的值吗?
  • @user3629249:如果您编写 SQL 代码,x BETWEEN y AND z 运算符包括范围内的端点 (x &gt;= y AND x &lt;= z)。在简单的英语中,不清楚“在……和”之间是否不包括端点。当然,在细微不同的“你我之间”这句话中,包括了两个人;如果您说“选择一个介于 1 和 100 之间的数字”,您通常不会对选择 1 或 100 感到不安。您可能会批评这个问题含糊不清,但不清楚“介于 0 和 81 之间”是否会自动排除0 和 81。

标签: c random


【解决方案1】:

您的代码有我见过的最奇怪的缩进和大括号布局方案之一:

#include <stdio.h>
#include <stdlib.h>

int getRandomNumber(int Min, int Max)
{
    double rnd= (double)rand()/((double)RAND_MAX+1);
    return   (int)(rnd*(Max-Min+1))+Min;
}

int main()
{
    int j,counter,temp,deck[13];
    srand(time(NULL));
    int i;
    counter=1;
    for (i=0;i<12;i++)
        {deck[i]=-1;
        temp = getRandomNumber(0,81);

        for (j=0;j<=i;j++)
            {if (temp==deck[j])
                {counter=0;}
            if (counter!=0)
                deck[i]=temp;
            }
        }
    for(i=0;i<12;i++)
        printf("%d ",deck[i]);

}

转换为更正统的风格(Allman,或多或少——参见维基百科Indent style),你会得到:

#include <stdio.h>
#include <stdlib.h>

static int getRandomNumber(int Min, int Max)
{
    double rnd = (double)rand() / ((double)RAND_MAX + 1);
    return (int)(rnd * (Max - Min + 1)) + Min;
}

int main(void)
{
    int j, counter, temp, deck[13];
    srand(time(NULL));
    int i;
    counter = 1;
    for (i = 0; i < 12; i++)
    {
        deck[i] = -1;
        temp = getRandomNumber(0, 81);

        for (j = 0; j <= i; j++)
        {
            if (temp == deck[j])
            {
                counter = 0;
            }
            if (counter != 0)
                deck[i] = temp;
        }
    }
    for (i = 0; i < 12; i++)
        printf("%d ", deck[i]);
}

需要staticint main(void) 才能使代码通过我的默认编译选项;否则,它们是化妆品。

现在我们可以看到一些问题。将counter 设置为1 一次,在外循环之外;它有时会在循环内设置为0,但一旦发生,它就永远不会重置为1,因此不会向deck 添加更多数字。你需要重做内部循环,可能是这样的:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

static int getRandomNumber(int Min, int Max)
{
    double rnd = (double)rand() / ((double)RAND_MAX + 1);
    return (int)(rnd * (Max - Min + 1)) + Min;
}

int main(void)
{
    int deck[13];
    srand(time(NULL));
    for (int i = 0; i < 12; i++)
    {
        int temp = getRandomNumber(0, 81);
        deck[i] = -1;

        int counter = 1;
        for (int j = 0; j <= i; j++)
        {
            if (temp == deck[j])
            {
                counter = 0;
                break;
            }
        }
        if (counter != 0)
            deck[i] = temp;
        else
            i--;  // Try again with a new random choice for the same i
    }
    const char *pad = "";
    for (int i = 0; i < 12; i++)
    {
        printf("%s%d", pad, deck[i]);
        pad = " ";
    }
    putchar('\n');
    return 0;
}

我不喜欢尾随空格,因此打印循环会注意确保没有尾随空格。

getRandomNumber() 的示例输出 — 在 macOS Sierra 10.12.2 和 GCC 6.3.0 上:

7 73 38 61 11 13 41 66 29 39 72 20
7 2 18 17 54 31 45 40 34 22 63 16
7 13 80 54 16 49 14 58 28 53 23 26
7 24 60 10 67 53 69 32 23 2 66 12
7 34 40 48 21 3 57 43 6 18 27 80
7 45 20 3 65 21 61 17 12 69 66 27
7 67 62 78 70 57 68 46 9 2 72 39
7 77 41 34 32 75 72 20 64 78 33 25
7 6 21 72 76 11 75 38 73 27 64 33
7 17 1 27 37 28 80 49 12 67 59 36

第一个数字并不是很随机——在一系列测试之后不久,它从 7 变为 8,但同样非随机。替代 生成随机数的方法是:

static int getRandomNumber(int Min, int Max)
{
    int rnd;
    int range = (Max - Min + 1);

    while ((rnd = rand()) > RAND_MAX - (RAND_MAX % range))
        ;

    return (rnd % range) + Min;
}

这避免了 82 不完全除以 RAND_MAX 这一事实的偏差,这将使较低数字的分布权重略高于 0..81 范围内的较高数字。它还避免了意外一致的第一个数字,尽管当测试以 1 秒的间隔运行时,新的第一个数字也是半可预测的。

示例结果:

48 33 28 78 14 2 81 13 23 75 38 40
45 42 74 1 11 68 17 33 78 49 23 80
42 51 38 3 5 52 35 56 54 23 59 41
39 60 2 8 36 53 79 30 72 75 62 37
36 69 45 10 78 20 71 17 6 53 54 30
33 78 9 15 75 7 40 61 27 36 70 68
30 5 55 17 69 73 25 63 37 1 21 71
27 14 19 66 57 43 1 13 3 65 71 21
24 26 62 63 41 61 68 28 67 20 74 17
21 35 26 57 28 79 47 44 2 52 60 77

注意第一个数字减3;第二个似乎增加了 9;呃——随机性并不是那么好。众所周知,rand() 通常不是一个高质量的 PRNG(伪随机数生成器),但我对这种明显的系统行为感到有点惊讶,种子每次相差 1。

在我的 Mac 上,当我将 srand() 更改为 srandom() 并将 rand() 更改为 random() 时,我得到了更好的(如,更不可预测的)结果:

29 1 7 11 25 52 63 15 26 55 75 64
40 4 64 18 8 57 73 27 38 15 60 28
43 3 27 17 1 58 26 72 73 18 20 7
76 16 27 43 64 20 63 30 35 17 33 57
79 47 32 33 6 30 35 7 38 55 25 61
69 57 79 75 15 54 5 35 21 46 65 61
30 79 66 14 56 39 19 8 50 47 76 33
62 65 81 44 52 39 25 30 54 12 24 68
27 49 60 72 53 35 14 41 63 46 45 65
67 39 9 11 60 19 64 73 43 17 21 26

random() 的 Mac 手册页仍然建议使用arc4random(),但这比普通的rand() 要好得多。您在其他系统上找到的内容将取决于系统提供的设施 — rand() 可能不像在 Mac 上看起来那么糟糕。基本上,在选择 PRNG 时要谨慎——尤其是如果您要使用系统生成的种子(例如当前时间)。

【讨论】:

    【解决方案2】:

    出于您建议的目的(生成从 0 到 81 的随机数字序列,其中每个项目都不同),您将需要一种特定类型的随机数生成器 (RNG),它可以生成所有可能的82 个项目的排列(表示为82!,或 82 阶乘)。但是,只有有限的 RNG 选择可以做到这一点。特别是 C rand() 函数的实现是未指定的,因此不能保证生成那么多排列。

    伪随机数生成器(PRNG,这里使用的一种 RNG)不能生成比其周期更多的随机数序列。对于82! 排列,没有周期小于82! 的PRNG 可以做到这一点(2 的下一个最高幂是2408,这意味着生成器需要至少408 位的种子,或 51 个字节,它可能会这样做——而且 51 个字节比srand 通常可以占用的要大得多)。或者,出于此目的,建议您使用生成“不可预测”数字的 RNG,C 语言和 C 库都没有将其作为标准包含在内。有关详细信息,请参阅 my article on randomness 中的“改组”和“不可预测的 RNG”。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-09-24
      • 2014-09-08
      • 1970-01-01
      • 2017-04-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多