使用 rand() 获取数字但该数字不能是上次生成的数字
Using rand() to get a number but that number can't be the number that was last generated
我想用std::rand()
生成一个介于0
和amountOfNumbers
之间的号码,但是生成的号码不能与上次生成的号码相同。
我写了这个函数:
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)
让我有点不安。
代码有效,但我会像这样构造它
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,我假设它们是全局的。最好将它们作为变量传递给函数。此外,您应该 return 函数中的新数字,而不是将其设置为无效。
下面的代码将计算新的滚动。我们不会重新滚动直到有新的滚动,我们只会随机取一组数字,但少一个。如果数字大于或等于,我们将加回一个,从而避免最后一个数字。然后函数 returns newRand。这样做将避免(尽管很低)无限循环的风险,并且它总是 运行 在常数时间内。
int reroll(int lastNumber, int amountOfNumbers)
{
int newRand;
newRand = std::rand() % (amountOfNumbers - 1);
if (newRand >= lastNumber) {
newRand++;
}
return newRand;
}
while 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。您会看到值 0
和 1
是其他人的两倍!实际上,RAND_MAX
必须至少为 32767(不管那是什么意思,根据标准这并不多)...但是 that 不能被整除6,所以当然会有一些值有轻微的 bias.
您可以使用模数来缩小该范围,但您可能希望在第二个问题中放弃偏差。不幸的是,由于 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;
}
在技术说明上,这也不一定保证解决方案会出现问题。标准中的这句话解释了原因:
There are no guarantees as to the quality of the random sequence produced and some implementations are known to produce sequences with distressingly non-random low-order bits. Applications with particular requirements should use a generator that is known to be sufficient for their needs.
因此,一些愚蠢、晦涩的实现可能会像这样实现 rand
:
int rand(void) {
return 0;
}
对于这样的实现,无法避免:rand
的这种实现会导致此答案(以及所有其他当前答案)中的代码进入 无限循环。您唯一的希望是重新实现 rand
和 srand
。这是标准中给出的示例实现,如果您必须这样做:
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_rand
和 my_srand
,并使用 #define rand my_rand
和 #define srand my_srand
或使用 find/replace 操作。
我想用std::rand()
生成一个介于0
和amountOfNumbers
之间的号码,但是生成的号码不能与上次生成的号码相同。
我写了这个函数:
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)
让我有点不安。
代码有效,但我会像这样构造它
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,我假设它们是全局的。最好将它们作为变量传递给函数。此外,您应该 return 函数中的新数字,而不是将其设置为无效。
下面的代码将计算新的滚动。我们不会重新滚动直到有新的滚动,我们只会随机取一组数字,但少一个。如果数字大于或等于,我们将加回一个,从而避免最后一个数字。然后函数 returns newRand。这样做将避免(尽管很低)无限循环的风险,并且它总是 运行 在常数时间内。
int reroll(int lastNumber, int amountOfNumbers)
{
int newRand;
newRand = std::rand() % (amountOfNumbers - 1);
if (newRand >= lastNumber) {
newRand++;
}
return newRand;
}
while 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。您会看到值0
和1
是其他人的两倍!实际上,RAND_MAX
必须至少为 32767(不管那是什么意思,根据标准这并不多)...但是 that 不能被整除6,所以当然会有一些值有轻微的 bias.
您可以使用模数来缩小该范围,但您可能希望在第二个问题中放弃偏差。不幸的是,由于 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;
}
在技术说明上,这也不一定保证解决方案会出现问题。标准中的这句话解释了原因:
There are no guarantees as to the quality of the random sequence produced and some implementations are known to produce sequences with distressingly non-random low-order bits. Applications with particular requirements should use a generator that is known to be sufficient for their needs.
因此,一些愚蠢、晦涩的实现可能会像这样实现 rand
:
int rand(void) {
return 0;
}
对于这样的实现,无法避免:rand
的这种实现会导致此答案(以及所有其他当前答案)中的代码进入 无限循环。您唯一的希望是重新实现 rand
和 srand
。这是标准中给出的示例实现,如果您必须这样做:
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_rand
和 my_srand
,并使用 #define rand my_rand
和 #define srand my_srand
或使用 find/replace 操作。