在 (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
是您的样本大小(您要选择的元素数量)。
如果N
比M
大得多,您将需要使用Bentley 和Floyd 在他的专栏“Programming Pearls: a sample of brilliance”中建议的算法(暂时不使用ACM 的锁屏here),我真的推荐这个,因为他们明确地给出了代码并就哈希表等进行了讨论;里面有一些巧妙的技巧
如果你真的不知道page 647 of Devroye's book on random generation is pretty fast上的算法。
【讨论】:
以上是关于在 (0 - X) 范围内生成唯一编号,保留历史记录以防止重复的主要内容,如果未能解决你的问题,请参考以下文章