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

IT技术 javascript jquery arrays random
2021-01-31 22:07:35

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

或者,当这完成后(例如,范围已“用尽”),只需返回范围内的随机数。

怎么做呢?

4个回答

这应该这样做:

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 个随机数时它没有很好的性能——它在整个列表中搜索一个空闲位置。这个算法是一个逐步的Fisher-Yates shuffle,来自问题唯一(非重复)随机数在 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;
    };
}

演示在 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);
        }
    };
}

演示

数量受其他脚本影响
2021-03-16 22:07:35
坦白说,我不太明白你的做法。如何在每次generate调用方法时传入不同的范围这是一个需要的功能还是一个错误?
2021-03-18 22:07:35
不,它稳定地产生范围内的随机数0-X如果输出按每个 X 数字分组,则在每个组中您都不会发现重复项。这“过度满足”了您的要求,即只有第一组(结果 0 到 X-1)需要是唯一的。
2021-03-21 22:07:35
是的,现在我明白了。但是在我的函数中清除 'NumHistory' 数组不会做同样的事情吗?
2021-03-31 22:07:35
效果很好!(并且对于大范围快速),但是在创建初始唯一数字后它不会继续生成随机数。我可能错了,但您的代码仅输出固定数量。在我的特定情况下,我需要在 0-X 范围内无限提供随机数,每当用户按下按钮时,只有第一个 X 必须是唯一的。
2021-04-01 22:07:35

你得到了一些很棒的编程答案。这是一个更具理论风味的作品来完成您的全景图:-)

您的问题被称为“抽样”或“子集抽样”,您可以通过多种方式做到这一点。N成为您抽样框的范围(即N=X+1),并M成为您的样本大小(您要选择的元素数量)。

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

// 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所指出的,它可能会在大范围内变慢(这是一个 小提琴,范围从 0-1000,这似乎运行良好)。然而; 我不需要很大的范围,所以如果您希望生成和跟踪一个很大的范围,这个功能可能确实不适合。

问题不在于数字范围,而在于您将生成的数字数量。生成 n 个数字需要 O(n^2) 时间。您应该使用 Object ( {}) 而不是数组并将已经获取的数字存储为键。
2021-03-18 22:07:35
根据这个这个问题,似乎没有办法在每个数字的 O(1) 时间内为可变间隔创建唯一的随机数。所以我想除非 maxNum 是一个常数,否则不会有更好的解决方案。
2021-03-23 22:07:35
好点子。我真的不知道它将如何处理大范围。我已经编辑了我的答案,其中包含一个指向使用 setTimeout 生成 0-1000 范围内的唯一值的小提琴的链接。它似乎运行良好,但这可能是由于我机器的处理能力
2021-03-24 22:07:35
JS 数组是对象(带有字符串键的哈希表),并且两者的键查找时间都是 O(1)(几乎是即时的)。但在这种情况下,您正在按值查找,这是一个 for 循环,它 1 x 1 检查所有值。
2021-04-04 22:07:35
这可能工作正常,但效率不高。如果 X 很大,这将在不久后变得非常慢。
2021-04-11 22:07:35

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