Javascript 生成范围(0-X)内的唯一编号,保留历史记录以防止重复

Javascript 生成范围(0-X)内的唯一编号,保留历史记录以防止重复,javascript,jquery,arrays,random,Javascript,Jquery,Arrays,Random,我遇到了一个挑战,我需要一个函数,返回一个给定范围内的随机数,从0-X。不仅如此,我还要求返回的数字必须是唯一的;不复制以前调用函数时已返回的数字 或者,当完成此操作(例如,范围已“耗尽”)时,只需返回范围内的随机数 如何才能做到这一点呢?我编写了这个函数。它使用生成的数字的历史记录保留自己的数组,防止初始重复,如果范围内的所有数字都输出一次,则继续输出随机数: // Generates a unique number from a range // keeps track of generat

我遇到了一个挑战,我需要一个函数,返回一个给定范围内的随机数,从
0-X
。不仅如此,我还要求返回的数字必须是唯一的;不复制以前调用函数时已返回的数字

或者,当完成此操作(例如,范围已“耗尽”)时,只需返回范围内的随机数


如何才能做到这一点呢?

我编写了这个函数。它使用生成的数字的历史记录保留自己的数组,防止初始重复,如果范围内的所有数字都输出一次,则继续输出随机数:

// 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;
        }
    }
};

我希望这对某人有用

编辑:正如下面指出的,它可能在大范围内变慢(下面是一个例子) ,似乎运行良好)。然而;我不需要很大的范围,所以如果您希望生成并跟踪一个很大的范围,那么这个函数可能确实不适合。

这应该可以做到:

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;
        }
    };
}

虽然它可以工作,但当生成第x个随机数时,它没有良好的性能-它会搜索整个列表中的一个空闲位置,从问题开始,一步一步地做会更好:

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);
        }
    };
}
函数makeRandomRange(x){
var范围=新阵列(x),
指针=x;
返回函数getRandom(){
如果(范围){
指针--;
var random=Math.floor(Math.random()*指针);
var num=(范围内随机)?范围[随机]:随机;
范围[随机]=(指针在范围内)?范围[指针]:指针;
范围[指针]=num;

如果(指针您可以尝试使用当前日期和时间值生成数字,这将使其唯一。要使其在范围内,您可能需要使用一些数学函数。

您得到了一些很棒的编程答案。下面是一个更具理论色彩的答案,以完成您的全景图:-)

您的问题称为“采样”或“子集采样”,有几种方法可以做到这一点。让
N
为采样帧的范围(即
N=X+1
)和
M
为采样的大小(要拾取的元素数)

  • 如果
    N
    M
    大得多,您将需要使用Bentley和Floyd在其专栏“”中建议的算法(暂时没有ACM的锁屏),我真的建议这样做,因为他们明确给出代码,并根据哈希表等进行讨论;这里有一些巧妙的技巧

  • 如果
    N
    M
    在同一范围内,则您可能希望使用,但仅在
    M
    步骤后停止(而不是
    N

  • 如果你真的不知道,那么算法就开始了


如果您希望经常这样做,并且
X
不是太大,您可以使用
0…X
值构建一个数组,然后将其洗牌。然后您可以迭代数组以获得随机值,并在到达末尾时重新洗牌。这可能很好,但效率不是很高。如果X很大,则会得到随机值不太长时间后速度会很慢。说得好。我真的不知道它将如何处理大范围。我用一个链接编辑了我的答案,该链接指向一个fiddle,该fiddle使用setTimeout生成0-1000范围内的唯一值。它似乎运行正常,但这可能是因为我的机器的处理能力问题不在数字范围内t您将生成的数字量。生成n个数字需要O(n^2)时间。您应该使用对象(
{}
)而不是数组,并将已获取的数字存储为键。JS数组是对象(带有字符串键的哈希表),两个数组的键查找时间都是O(1)(非常即时).但在本例中,您正在按值进行查找,这是一个for循环,它检查所有的值1乘1。根据和问题,似乎没有一种方法可以在每个数字的O(1)时间内为可变间隔创建唯一的随机数。因此,我想不会有更好的解决方案,除非maxNum是一个常量。效果很好!(适用于大范围,速度快),但在创建初始唯一数后,它不会继续生成随机数。我可能错了,但您的代码只输出固定数量。在我的特定情况下,每当用户按下按钮时,我需要在0-X范围内无限提供随机数,并且只有第一个X必须是唯一的。不,它稳定地生成范围
0-X
中的随机数。如果输出按每X个数字分组,则在每个组中都不会找到重复的数字。这“超额满足”了您的要求,即仅第一组(结果为0到X-1)需要是唯一的。是的,现在我明白了。但是在我的函数中清除“NumHistory”数组不也是这样吗?坦白说,我真的不理解你的方法。每次调用
generate
方法时,你怎么能传递不同的范围?这是一个必需的功能还是一个bug?数量受其他脚本的影响
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);
        }
    };
}