随机数,不等于前一个数

IT技术 javascript
2021-01-28 17:54:40

我需要获取随机数,但它不应该等于前一个数字。这是我的一段代码。但它不起作用。

function getNumber(){
  var min = 0;
  var max = 4;
  var i;
  i = Math.floor(Math.random() * (max - min)) + min;
  if (i=== i) {
    i = Math.floor(Math.random() * (max - min)) + min;
  }
  return i;
};

console.log(getNumber());
6个回答

这个答案提出了三种尝试

  1. 具有函数属性的简单版本getNumber, last,用于存储最后一个随机值。

  2. minmax值上使用闭包引发异常 ifmax小于 的版本min

  3. 一个结合了闭包和保留所有随机值的想法的版本,并在适当的时候使用它。


您可以使用属性getNumber来存储最后一个数字并使用do ... while循环。


另一个建议在区间和最后一个随机值上有一个闭包。


这个提议使用了最小化新随机数调用的想法。它适用于两个变量,value用于连续相同的随机值和count保存相同值的计数。

如果给出了保存的计数并且该值与最后一个值不相等,则该函数首先查找。如果发生这种情况,则返回保存的值并递减计数。

否则会生成一个新的随机数并按上述方式检查(第一个提议)。如果数字等于最后一个值,则计数增加并继续生成新的随机值。

结果,几乎所有以前生成的随机值都被使用。

当前实现调用getNumber超过 100 次plnkr.co/edit/3GHnnhbkf5F8f0fRx9mX?p=preview您可以删除循环,在if语句中使用逻辑条件来增加或减少igetNumber.last 调用getNumber等于for循环调用以返回预期结果 plnkr.co/edit/3GHnnhbkf5F8f0fRx9mX?p=preview
2021-03-18 17:54:40
哦,我想,我明白了。最后一个 for 循环仅用于演示目的,并且getNumber只调用 100 次,不会更多,因为您也计算 do 循环。
2021-03-19 17:54:40
我不明白你。你的意思是哪个 if 条件(没有)?
2021-03-26 17:54:40
我打电话只是一个属性getNumbergetNumber.last又不是功能。
2021-04-04 17:54:40
你可以使用替代逻辑在if其中的递增或递减条件下igetNumber.lastdo..while循环,消除需要额外的呼叫getNumber; 另请参阅特定范围内的随机整数,不包括一个数字
2021-04-07 17:54:40

下面的方法在 [min, max] 范围内生成一个新的随机数,并确保这个数字与前一个不同,没有循环和递归调用(Math.random()只调用一次):

  • 如果存在先前的数字,则将 max 减一
  • 在范围内生成一个新的随机数
  • 如果新数等于或大于前一个,则加一个
    (另一种选择:如果新数等于前一个,则将其设置为 max + 1)

为了在闭包中保留之前的数字,getNumber可以在 IIFE 中创建:

// getNumber generates a different random number in the inclusive range [0, 4]
var getNumber = (function() {
  var previous = NaN;
  return function() {
    var min = 0;
    var max = 4 + (!isNaN(previous) ? -1 : 0);
    var value = Math.floor(Math.random() * (max - min + 1)) + min;
    if (value >= previous) {
      value += 1;
    }
    previous = value;
    return value;
  };
})();

// Test: generate 100 numbers
for (var i = 0; i < 100; i++) {
  console.log(getNumber());
}
.as-console-wrapper {
  max-height: 100% !important;
  top: 0;
}

在[MIN,MAX]范围是由包括通过加入1至max - min在下面的语句:

var value = Math.floor(Math.random() * (max - min + 1)) + min;

这不是问题中的要求,但我觉得使用包含范围更自然。

@TomasLangkaas - 感谢您的建议。我改写了我的答案,希望现在更清楚。
2021-03-24 17:54:40
@ConnorsFan,您可能想明确说明您的方法保证从不包括前一个数字的一​​组数字中随机抽取一个数字,即使在抽取的数字是前一个数字的情况下也是如此。就目前而言,它可能会被误解为牺牲随机性的简单调整。
2021-04-04 17:54:40
我不认为这段代码即提供一个接受minmax作为参数的函数是真正安全的。如果您的函数接受参数,则意味着您需要多个消费者。在这种情况下,不同的消费者会弄乱previous彼此的状态 ( ),从而为同一个消费者生成两个连续相等的值成为可能。我认为最简单安全的方法是让一个工厂函数createGetNumber,将接受minmax和不带参数被唯一绑定到一个状态恢复的功能。
2021-04-06 17:54:40
@SergGr - 感谢您的评论。我可以把背部minmax内部的功能,因为提供更改这些限制不是必需的能力。
2021-04-06 17:54:40

首先函数应该与之前的值进行比较,现在我们只有i与自身进行比较的变量。为了确保我们没有先前的值,我们需要在内部进行循环(在我的解决方案中是递归的),因为 singleif statement不能让我们确定第二个随机数将不相同(存在机会)。您的数字集非常小,因此碰撞的机会很高,并且循环可能需要很少的执行。

function getNumber(prev){
  var min = 0;
  var max = 4;
  var next;
  
  next = Math.floor(Math.random() * (max - min)) + min;
  
  if (next===prev) {
    console.log("--run recursion. Our next is ="+next); //log only for test case
    next = getNumber(prev); //recursive
  }
  
  return next;
};

//test 100 times
var num=0;
for ( var i=0; i<100; i++){
  num=getNumber(num);
  console.log(num);
}

正如你在测试中看到的,我们从来没有两个相同的值彼此相邻。我还添加了一些console.log来显示递归需要运行多少次才能找到与前一个不同的下一个数字。

通用解决方案

跟踪最后生成的数字。生成新数字时,请检查它是否与上一个不同。如果不是,继续生成新数字,直到它不同,然后输出它。

工作演示

var getNumber = (function(){
  var min = 0;
  var max = 4;
  var last = -1;
  return function(){
    var current;
    do{
      // draw a random number from the range [min, max]
      current = Math.floor(Math.random() * (max + 1 - min)) + min;
    } while(current === last)
    return (last = current);
  }
})();

// generate a sequence of 100 numbers,
// see that they all differ from the last

for(var test = [], i = 0; i < 100; i++){
  test[i] = getNumber();
}
console.log(test);

关于计算效率的评论

正如评论和其他答案中所讨论的那样,上述方法的一个潜在缺点是,如果生成的数字等于前一个,则可能需要多次尝试来生成随机数。请注意,需要多次尝试的概率非常低(它遵循快速下降的几何分布)。出于实际目的,这不太可能产生任何明显的影响。

但是,可以通过直接从 [min, max] 范围内的一组数字中抽取一个随机数来避免多次尝试生成新的随机数,不包括先前抽取的数字:@@的答案中很好地证明了这一点ConnorsFan,每次函数调用只生成一个随机数,而随机性仍然保留。

你是对的。未指定为赏金要求的一部分。前段时间在stackoverflow.com/questions/40056297/... 的评论中注意到投票将决定目前的赏金stackoverflow.com/questions/40056297/...
2021-03-24 17:54:40
这不是 OP 的问题,尽管在几个答案中注意到了副作用,包括该用户的第一个答案stackoverflow.com/a/40057625理想情况下,只需调用 100 次函数或循环即可满足要求,例如,请参阅stackoverflow.com/a/40102421
2021-03-30 17:54:40
getNumber生成 100 个号码需要调用多少次?
2021-04-01 17:54:40
@guest271314,我没有得到那个要求。在这种情况下,ConnorsFan 似乎做对了。
2021-04-08 17:54:40
getNumber为每个数字调用一次,但do/while循环理论上可以运行一段时间。实际上,它通常只运行一次。有 5 个可能的数字,大约需要五分之一的重复才能得到不同的数字。
2021-04-11 17:54:40

您将需要一个范围比getNumber函数本地变量更大的变量尝试:

var j;
function getNumber(){
  var min = 0;
  var max = 4;
  var i = Math.floor(Math.random() * (max - min)) + min;
  if (j === i) {
    i = getNumber();
  }
  j = i;
  return i;
};