我遇到了一个挑战,我需要一个函数,它返回0 - X
给定范围内的随机数。不仅如此,我还要求返回的数字是唯一;不复制先前调用该函数时已返回的数字。
可选地,当完成此操作时(例如,范围已经“耗尽”),只需返回范围内的随机数。
如何做到这一点?
答案 0 :(得分:3)
这应该这样做:
function makeRandomRange(x) {
var used = new Array(x),
exhausted = false;
return function getRandom() {
var random = Math.floor(Math.random() * x);
if (exhausted) {
return random;
} else {
for (var i=0; i<x; i++) {
random = (random + 1) % x;
if (random in used)
continue;
used[random] = true;
return random;
}
// no free place found
exhausted = true;
used = null; // free memory
return random;
}
};
}
用法:
var generate = makeRandomRange(20);
var x1 = generate(),
x2 = generate(),
...
虽然它有效但是在生成第x个随机数时没有良好的性能 - 它会在整个列表中搜索一个空闲的位置。来自问题This algorithm的Fisher–Yates shuffle,一步一步Unique (non-repeating) random numbers in O(1)?,效果会更好:
function makeRandomRange(x) {
var range = new Array(x),
pointer = x;
return function getRandom() {
pointer = (pointer-1+x) % x;
var random = Math.floor(Math.random() * pointer);
var num = (random in range) ? range[random] : random;
range[random] = (pointer in range) ? range[pointer] : pointer;
return range[pointer] = num;
};
}
只生成一个唯一数字“组”的扩展版本:
function makeRandomRange(x) {
var range = new Array(x),
pointer = x;
return function getRandom() {
if (range) {
pointer--;
var random = Math.floor(Math.random() * pointer);
var num = (random in range) ? range[random] : random;
range[random] = (pointer in range) ? range[pointer] : pointer;
range[pointer] = num;
if (pointer <= 0) { // first x numbers had been unique
range = null; // free memory;
}
return num;
} else {
return Math.floor(Math.random() * x);
}
};
}
(Demo)
答案 1 :(得分:3)
你有一些很好的编程答案。这是一个具有更多理论风味的完整全景图: - )
您的问题被称为&#34;采样&#34;或&#34;子集采样&#34;并且有几种方法可以做到这一点。让N
为您抽样的范围(即N=X+1
),M
为样本的大小(您要选择的元素数)。
如果N
比M
大得多,您会想要使用Bentley和Floyd在其专栏中提出的算法&#34; {{ 3}}&#34; (暂时不使用ACM的锁定屏幕Programming Pearls: a sample of brilliance),我真的推荐这个,因为他们明确地给出代码并讨论哈希表等;那里有一些巧妙的技巧
如果N
与M
的范围相同,那么您可能希望使用here,但仅在M
步骤后停止(而不是{ {1}})
如果你真的不知道Fisher-Yates shuffle上的算法。
答案 2 :(得分:2)
我写了这个函数。它保留自己的数组,包含生成数字的历史记录,防止初始重复,如果范围内的所有数字都输出一次,则继续输出随机数:
// Generates a unique number from a range
// keeps track of generated numbers in a history array
// if all numbers in the range have been returned once, keep outputting random numbers within the range
var UniqueRandom = { NumHistory: new Array(), generate: function(maxNum) {
var current = Math.round(Math.random()*(maxNum-1));
if (maxNum > 1 && this.NumHistory.length > 0) {
if (this.NumHistory.length != maxNum) {
while($.inArray(current, this.NumHistory) != -1) { current = Math.round(Math.random()*(maxNum-1)); }
this.NumHistory.push(current);
return current;
} else {
//unique numbers done, continue outputting random numbers, or we could reset the history array (NumHistory = [];)
return current;
}
} else {
//first time only
this.NumHistory.push(current);
return current;
}
}
};
我希望这对某人有用!
编辑:正如下面的Pointy指出的那样,它可能会在很大的范围内变慢(这是一个 fiddle, going over a range from 0-1000,似乎运行良好)。然而;我不需要非常大的范围,所以如果你想要产生并跟踪一个巨大的范围,这个功能可能确实不适合。
答案 3 :(得分:0)
您可以尝试使用当前日期和时间值生成数字,这将使其唯一。要使它在范围内,您可能必须使用一些数学函数。