在JS中生成非重复随机数

我有以下function

function randomNum(max, used){ newNum = Math.floor(Math.random() * max + 1); if($.inArray(newNum, used) === -1){ console.log(newNum + " is not in array"); return newNum; }else{ return randomNum(max,used); } } 

基本上我创建一个1到10之间的随机数,并通过将其添加到数组并检查新创建的数字来检查是否已创建该数字。 我通过将其添加到变量来调用它。

 UPDATED: for(var i=0;i < 10;i++){ randNum = randomNum(10, usedNums); usedNums.push(randNum); //do something with ranNum } 

这有效,但在Chrome中我收到以下错误:

 Uncaught RangeError: Maximum call stack size exceeded 

我想这是因为我在内部调用函数的次数太多了。 这意味着我的代码不好。

有人可以帮我逻辑吗? 什么是确保我的数字不重复的最佳方法?

如果我理解的话,你只是在寻找数字1-10的排列(即随机数没有重复)? 也许尝试生成这些数字的随机列表,一次,在开始时,然后只是通过这些?

这将计算nums数字的随机排列:

 var nums = [1,2,3,4,5,6,7,8,9,10], ranNums = [], i = nums.length, j = 0; while (i--) { j = Math.floor(Math.random() * (i+1)); ranNums.push(nums[j]); nums.splice(j,1); } 

因此,例如,如果您正在寻找1到20之间的偶数,那么您可以使用:

 nums = [2,4,6,8,10,12,14,16,18,20]; 

然后只需通读ranNums以便回忆随机数。

正如您在方法中所发现的那样,不会花费更长的时间来查找未使用的数字。

编辑 :阅读完这篇并在jsperf上运行测试后 ,看起来更好的方法是做一个Fisher-Yates Shuffle:

 function shuffle(array) { var i = array.length, j = 0, temp; while (i--) { j = Math.floor(Math.random() * (i+1)); // swap randomly chosen element with current element temp = array[i]; array[i] = array[j]; array[j] = temp; } return array; } var ranNums = shuffle([1,2,3,4,5,6,7,8,9,10]); 

基本上,通过避免使用“昂贵的”arrays操作来提高效率。

奖励编辑 :另一种可能性是使用发电机 (假设您有支持 ):

 function* shuffle(array) { var i = array.length; while (i--) { yield array.splice(Math.floor(Math.random() * (i+1)), 1)[0]; } } 

然后使用:

 var ranNums = shuffle([1,2,3,4,5,6,7,8,9,10]); ranNums.next().value; // first random number from array ranNums.next().value; // second random number from array ranNums.next().value; // etc. 

一旦你运行了混洗数组中的所有元素, ranNums.next().value最终将计算为undefined

总的来说,这不会像Fisher-Yates Shuffle那样有效,因为你仍在splice一个arrays。 但不同之处在于,您现在只在需要时才开始做这项工作,而不是事先做好,所以根据您的使用情况,这可能会更好。

问题是,当您接近饱和状态时,您会开始花费更长时间来“随机”生成唯一数字。 例如,在上面提供的示例中,max是10.一旦使用的数字数组包含8个数字,可能需要很长时间才能找到第9个和第10个数字。 这可能是生成最大调用堆栈错误的地方。

jsFiddle Demo showing iteration count being maxed

通过在递归内部迭代,您可以看到当数组完全饱和时会发生大量执行,但会调用该函数。 在这种情况下,该函数应退出。

jsFiddle Demo with early break

 if( used.length >= max ) return undefined; 

完成迭代检查和无限递归的最后一种方法就像这个jsFiddle Demo

 function randomNum(max, used, calls){ if( calls == void 0 ) calls = 0; if( calls++ > 10000 ) return undefined; if( used.length >= max ) return undefined; var newNum = Math.floor(Math.random() * max + 1); if($.inArray(newNum, used) === -1){ return newNum; }else{ return randomNum(max,used,calls); } } 

这将做你想要的:

 let anArrayOfUniqueNumbers = []; let numberGenerator = function(arr) { if (arr.length >= 10) return; let newNumber = Math.floor(Math.random() * 10 + 1); if (arr.indexOf(newNumber) < 0) { arr.push(newNumber); } numberGenerator(arr); }; numberGenerator(anArrayOfUniqueNumbers); 

我们有:

  • 一个新的arrays
  • 一个以数组作为参数的函数

这个function将:

  • 检查它运行的arrays是否已经有十个索引,如果没有:
  • 生成1-10之间的随机数
  • 如果该随机数尚未在数组中,请将其推入数组
  • 再次运行

由于保护子句( if (arr.length >= 10) return; ),一旦满足参数,该函数将停止执行。

 function randomNumbers(max) { function range(upTo) { var result = []; for(var i = 0; i < upTo; i++) result.push(i); return result; } function shuffle(o){ for(var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x); return o; } var myArr = shuffle(range(max)); return function() { return myArr.shift(); }; } 

建立一个小测试,在jsfiddle上试试这个:

 var randoms = randomNumbers(10), rand = randoms(), result = []; while(rand != null) { result.push(rand); rand = randoms(); } console.log(result); 

随机播放function由dzone.com提供 。

这就是我使用underscore.js实现它的方法

minmax获得n整数。 其中nsize参数。

 var randomNonRepeatingIntFromInterval = function(min, max, size) { var values = []; while (values.length < size) { values.push(Math.floor(Math.random() * ( max - min + 1) + min)); values = _.uniq(values); } return values; } 
    

JavaScript Math.random()

Math.random() returns a random number between 0 (included) and 1 (excluded):

对不起,这是一个旧问题的新答案,但这可以通过地图更有效地完成。 你所追求的是随机选择而不是非重复随机。 非重复随机是无意义的。

其中_a是集合,而r不是集合的一部分,我们lambda随机值r:

 function aRandom(f){ var r = Math.random(); aRandom._a[r] ? aRandom(f) : f(r,aRandom._a[r] = 1); } aRandom._a = {}; //usage: aRandom(function(r){ console.log(r) }); 

当浏览器变得迟缓时重新定义aRandom._a。 为了避免最终的迟缓,人们应该真正使用具有足够熵的UUID生成算法,以便重复的机会实际上为零,而不是暴力强制可分性。 我选择了函数名称aRandom,因为拉丁字母前缀A-表示“远离”。 由于使用的越多,输出越远离随机。 该function在Macbook上在2100毫秒内产生一百万个唯一值。

上述解决方案的优点是不需要限制该组。 同样,多个呼叫者可以同时使用它并假设它们的值与所有其他呼叫者不同。 这对于诸如保险没有重叠的噪声抖动分布这样的事情是很方便的。

但是,它也可以修改为返回整数,以便将ram的使用限制为所提供的长度:

 function aRandom(f,c){ var r = Math.floor(Math.random()*c); aRandom._a[r] ? aRandom(f,c) : f(r,aRandom._a[r] = 1); } aRandom._a = {}; //usage: var len = 10; var resultset = []; for(var i =0; i< len; i++){ aRandom(function(r){ resultset.push(r); }, len); } console.log(resultset); 

你真的不想丢失随机数。 真正的随机数必须能够重复。

真正随机的数字就像投掷骰子。 接下来会出现任何数字。

随机播放的数字就像绘制扑克牌。 每个号码只能出现一次。

你真正要求的是随机播放一个数字列表,然后使用洗牌列表中的第一个这么多数字。

考虑按顺序制作数字列表,然后使用随机数生成器从该列表的副本中随机选择一个数字。 每次都将所选数字放在新列表的末尾,并将其从旧列表的副本中删除,从而缩短该列表。 完成后,新列表将包含随机数字,旧列表的副本将为空。

或者,您可以选择所选的数字并立即使用它,通过删除使用的数字来缩短列表的副本。 因为您已从列表中删除了该号码,所以它无法再次出现。