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

Posted

技术标签:

【中文标题】在 (0 - X) 范围内生成唯一编号,保留历史记录以防止重复【英文标题】:Generate unique number within range (0 - X), keeping a history to prevent duplicates 【发布时间】:2012-08-02 06:35:08 【问题描述】:

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

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

要怎么做呢?

【问题讨论】:

如果您希望经常这样做,并且X 不是太大,您可以使用值0 ... X 构建一个数组,然后对其进行随机播放。然后,您可以遍历数组以获取随机值,并在到达末尾时重新洗牌。 【参考方案1】:

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

// 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,似乎运行良好)。然而;我不需要非常大的范围,所以如果您希望生成并跟踪一个巨大的范围,那么这个函数可能确实不适合。

【讨论】:

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

应该这样做:

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

(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)

【讨论】:

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

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

【讨论】:

【参考方案4】:

你得到了一些很棒的编程答案。这是一个更具理论色彩的全景图:-)

您的问题称为“抽样”或“子集抽样”,您可以通过多种方式来解决此问题。让N 是您的抽样范围(即N=X+1),M 是您的样本大小(您要选择的元素数量)。

如果NM 大得多,您将需要使用Bentley 和Floyd 在他的专栏“Programming Pearls: a sample of brilliance”中建议的算法(暂时不使用ACM 的锁屏here),我真的推荐这个,因为他们明确地给出了代码并就哈希表等进行了讨论;里面有一些巧妙的技巧

1234563 /li>

如果你真的不知道page 647 of Devroye's book on random generation is pretty fast上的算法。

【讨论】:

以上是关于在 (0 - X) 范围内生成唯一编号,保留历史记录以防止重复的主要内容,如果未能解决你的问题,请参考以下文章

在范围内生成'n'个唯一随机数[重复]

(C语言)一种简易记法:生成[a,b]范围内的随机整数

记一则自动删除x天前数据并插入历史表的procedure

数据库C#Java生成唯一GUID 方法

C#如何生成一个范围内的随机数,偏向范围的低端? [复制]

C++中随机数生成范围内