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

时间:2012-08-04 12:48:22

标签: javascript jquery arrays random

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

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

如何做到这一点?

4 个答案:

答案 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 algorithmFisher–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;
    };
}

Demo at jsfiddle.net

只生成一个唯一数字“组”的扩展版本:

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为样本的大小(您要选择的元素数)。

  • 如果NM大得多,您会想要使用Bentley和Floyd在其专栏中提出的算法&#34; {{ 3}}&#34; (暂时不使用ACM的锁定屏幕Programming Pearls: a sample of brilliance),我真的推荐这个,因为他们明确地给出代码并讨论哈希表等;那里有一些巧妙的技巧

  • 如果NM的范围相同,那么您可能希望使用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;
        }
    }
};

Here's a working Fiddle

我希望这对某人有用!

编辑:正如下面的Pointy指出的那样,它可能会在很大的范围内变慢(这是一个 fiddle, going over a range from 0-1000,似乎运行良好)。然而;我不需要非常大的范围,所以如果你想要产生并跟踪一个巨大的范围,这个功能可能确实不适合。

答案 3 :(得分:0)

您可以尝试使用当前日期和时间值生成数字,这将使其唯一。要使它在范围内,您可能必须使用一些数学函数。