对于我正在进行的项目,我需要一个Javascript函数,它会在给定范围内返回一个随机数,而不会重复,直到整个范围“耗尽”。由于没有这样的事情,我自己设法创造了它。
该功能还需要传递id
。这样,如果您需要多个随机数,每个都有自己的历史记录,id
会跟踪所有这些。
该功能有效,但我需要一些建议;
inArray()
以非常大的范围(maxNum
)值执行的速度有多快?我有一种感觉,大数字会降低函数的速度,因为它会随机化数字,直到它生成一个仍然“有效”的数字(即不在历史数组中)。但我无法想出另一种方法来做这件事.. 剧本:
var UniqueRandom = {
NumHistory: [],
generate: function (maxNum, id) {
if (!this.NumHistory[id]) this.NumHistory[id] = [];
if (maxNum >= 1) {
var current = Math.round(Math.random() * (maxNum - 1)), x = 0;
if (maxNum > 1 && this.NumHistory[id].length > 0) {
if (this.NumHistory[id].length !== maxNum) {
while ($.inArray(current, this.NumHistory[id]) !== -1) {
current = Math.round(Math.random() * (maxNum - 1));
x = x + 1;
}
this.NumHistory[id].push(current);
} else {
//reset
this.NumHistory[id] = [current];
}
} else {
//first time only
this.NumHistory[id].push(current);
}
return current;
} else {
return maxNum;
}
},
clear: function (id) {
this.NumHistory[id] = [];
}
};
用法是:( 100是范围(0-100),the_id是..好吧,id)
UniqueRandom.NumHistory[100, 'the_id']
我已经设置了一个带有演示的Fiddle。
答案 0 :(得分:4)
答案 1 :(得分:3)
我使用了杰克的代码并对其进行了调整以使用弹出数组方法。
function fisherYates ( myArray ) {
var i = myArray.length;
if ( i == 0 ) return false;
while ( --i ) {
var j = Math.floor( Math.random() * ( i + 1 ) );
var tempi = myArray[i];
var tempj = myArray[j];
myArray[i] = tempj;
myArray[j] = tempi;
}
}
function RandomGenerator(maxNum) {
this.max = maxNum;
this.initRandomArray();
}
RandomGenerator.prototype.generate = function() {
// if no more numbers available generate new array
if( this.left === 0 ) this.initRandomArray();
this.last = this.arr.pop();
this.left = this.arr.length;
this.history.push( this.last );
return this.last;
}
RandomGenerator.prototype.initRandomArray = function() {
this.arr = [];
this.history = [];
this.last = null;
this.left = this.max;
for( var i = 0; i < this.max; i++ ) {
this.arr.push( i );
}
fisherYates( this.arr );
}
var mygen = new RandomGenerator(100);
console.log( mygen.generate() );
我从here获得了fisherYates算法。
如果已在历史对象中找到新的随机数,则生成新随机数的方法将导致不必要的循环。
小提琴here
答案 2 :(得分:1)
我倾向于认为它确实不是最有效的。我没有立即得到//first time only
。
此外,您可以通过跳过else return ..
并将条件写成相反的方式来使代码更具可读性,例如:
if (maxNum >= 1) {
//code
} else {
return maxNum;
}
变为
if (maxNum < 1) { // or maybe even if maxNum == 0
return maxNum;
}
//code
您的x
变量似乎也是多余的。
答案 3 :(得分:1)
我可能会使用随机生成器的实际实例来实现它。这样可以保持每个发生器的历史记录分开。
function RandomGenerator(maxNum)
{
this.max = maxNum;
this.history = {};
this.histn = 0;
}
// generate random number in range [0..maxNum)
RandomGenerator.prototype.generate = function()
{
var value;
if (this.histn == this.max ) {
return false;
}
do {
value = Math.floor(Math.random() * this.max );
} while (this.history[value]);
this.history['' + value] = 1;
++this.histn;
return value;
}
var mygen = new RandomGenerator(100);
console.log(mygen.generate());
在我的实现中,我为历史选择了一个普通对象而不是数组;通过测试属性而不是$.inArray()
来测试之前是否生成了值。
答案 4 :(得分:0)
我同意Alex的观点,在大多数用例中,您需要生成所有值的数组,将它们随机播放,然后根据需要弹出它们。
以下是一个例子:
var getShuffledUniqueRandoms = function(count, suffix) {
var values = [];
for (var i = 1; i < count+1; i++) {
values.push(i + suffix);
}
// Shuffle function originally from:
//+ Jonas Raoni Soares Silva
//@ http://jsfromhell.com/array/shuffle [v1.0]
return (function(o){ //v1.0
for(var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
return o;
})(values);
}
var values = getShuffledUniqueRandoms(10, "index");
$('button').click(function() {
if (values.length == 0) {
$('body').append('<p>Out of values.</p>');
} else {
$('body').append('<p>' + values.pop() + '</p>');
}
});
使用这种算法,它具有更大的前期成本,但至少它有一个已知的时间来完成(大致为O(n))。
使用该算法,您不断检查数组中是否存在随机值,每次新迭代都会变得越来越慢。
现在,如果您的数据集总是相对较小,那么您的算法可以更好地工作,但是大于10左右的任何数据都会开始失去它的优势。