我正在寻找一种有效的算法,可以在一个范围内产生随机值,而不会重复。
在伪代码中:(在Rand类中)
Rand(long from, long to) {
this.from = from;
this.to = to;
// ...
}
long getNumber() {
// returns a random number in the [from, to] range
// which has never been returned before
}
用法:
Rand r = new Rand(1, 100000000);
long x = r.getNumber();
long y = r.getNumber();
...
从r.getNumber()返回的数字应始终与之前返回的数字不同
当然,如果返回to - from + 1
个数字,算法应该重新开始(或者错误 - 不管怎么说都不重要)。
请注意,范围可能非常大,因此随机排列的数组(最初包含[from, to]
个数字)可能会溢出内存。
答案 0 :(得分:5)
cypher是一对一映射,否则无法解密。因此,任何块密码都会将数字0,1,2,3,4,5 ......映射到不同的n位数,其中n是密码的块密码大小。
将一个简单的4轮Feistel cypher与你想要的任何(偶数)块大小放在一起相对容易。只有四轮,它会很快但不安全。或者使用Hasty Pudding cypher,它可以包含您想要的任何块大小。
无论你使用什么密码,只需加密数字0,1,2 ......,然后查看输出块。您可以丢弃超出所需范围的任何结果,并保证所有结果都是唯一的。
答案 1 :(得分:1)
执行此操作的一种方法是生成from和to之间的数字列表,随机删除这些数字,直到包空为止,此时重新填充。为了节省大范围的存储空间,您可以将拾取的数字记录到一个点(选择复制品时重新选择),因为最初选择复制品的概率应该很低。确定最佳转换点可能是一项实证练习。
编辑:更多想法。
对于真正巨大的范围,即使在内存限制下也不会提供良好的性能。一种想法可能是将候选人不是作为数字列表存储,而是作为间隔存储。所以,最初,你选择from和to,获得x1。下一次,从第一个子区间或第二个区间中选择一个数字,其概率与区间长度成比例。在每个步骤中,消除长度为零的间隔。这需要存储M + 2个整数(在最坏的情况下),其中M是绘制的数量,或者对于大N(在最坏的情况下)渐近的N / 2,其中N是初始间隔大小。不过有人可能会仔细检查我。
答案 2 :(得分:1)
如果您不要求最后出现间隔中的每个号码,您可以使用linear congruental generator:
int getNumber() {
seed = (seed * A + C) mod (to-from);
return seed + to;
}
这是期刊,新的期间从种子变得等于初始值开始,期间的长度取决于A和C的选择。
优点:O(1)时间和空间,缺点:不会出现间隔中的每个数字。
对于长度为2 ^ m的区间,请查看http://en.wikipedia.org/wiki/Linear_feedback_shift_register我没有使用它,但维基百科说它可能是最大长度,即你可以让所有数字(除了一个)出现在输出中。
答案 3 :(得分:1)
关于起点的一些想法:
1)假设你有一个函数f(x),它是1..N上的一个排列,其中N大于你的范围。如果将其应用于范围内的x,则可能会产生非法值 - 超出范围的值。您可以通过在非法值上再次调用f来定义范围内的排列。你最终会达到一个合法的价值,因为序列x,f(x),f ^ 2(x),f ^ 3(x)最终必须循环,如果最坏的情况发生在最坏的情况,它将返回到x。
2)有一些交换网络允许你在N个对象上产生特殊N的所有可能的排列 - 一个例子是http://en.wikipedia.org/wiki/Clos_network#Bene.C5.A1_network_.28m_.3D_n_.3D_2.29(有趣的URL - Benes网络)。通过随机设置开关,您可以在N个对象上获得任意排列,其中N可能认为必须是2的幂。由于将有K个开关,有2 ^ K方式设置它们,这意味着你没有M!具有相同概率的排列,但也许你不会介意这一点,或者可以通过重复多次或其他事情来最小化非随机性。
3)如果你准备通过多次使用许多不同的基本排列来实现接近随机性,你可以尝试在整个范围内交替添加mod N,然后将范围分成子范围,例如,对于该范围内的一些p-1值,应用通过乘以某个k模式p产生的置换。希望尽管每个步骤都是非常基本的,但是通过应用足够多的步骤并使它们足够多样化,结果将接近随机,特别是如果您随机选择参数。
答案 4 :(得分:1)
你可以这样做(在python中):
创建一个xrange并从中选择k随机元素并将其用作RanndomGenerator:
import random
def randomFromTo(FROM,TO,k): #k is the number of sample you want to generate
m= random.sample(xrange(FROM,TO),k)
return (i for i in m)
您的生成器将从FROM到TO随机生成所有数字,并在生成超过k个数字时失败:
通过这个例子你会得到:
RandomGenerator=randomFromTo(10,1000000000,12)
for k in range(12):
print RandomGenerator.next()
你得到了
57625960
50621599
2891457
56292598
54608718
45258991
24112743
55282480
28873528
1120483
56876700
98173231
答案 5 :(得分:1)
我会假装你要求R中的解决方案(根据标签)。你要做的是没有替换的样品。在R中,有一个名为sample
的函数。在这里,我正在采样一个30个值的矢量(1,2,3 ... 30),一旦我画了一个数字,它就不会被替换。您可以通过设置种子在其他计算机上使其可重现(请参阅set.seed
)。
我跑了好几次以显示随机性。
> sample(x = 1:30, size = 10, replace = FALSE)
[1] 9 16 13 20 12 3 1 5 28 7
> sample(x = 1:30, size = 10, replace = FALSE)
[1] 22 11 26 29 20 1 3 6 7 10
> sample(x = 1:30, size = 10, replace = FALSE)
[1] 1 11 16 7 22 26 3 25 8 9
> sample(x = 1:30, size = 10, replace = FALSE)
[1] 7 17 3 22 21 24 27 12 28 2
> sample(x = 1:30, size = 10, replace = FALSE)
[1] 30 21 23 2 27 24 3 18 25 19
> sample(x = 1:30, size = 10, replace = FALSE)
[1] 4 6 11 16 26 8 17 22 23 25
答案 6 :(得分:1)
cypher是一对一的映射,否则无法解密。 因此,任何块密码都将映射数字0,1,2,3,4,5 ...... 不同的n位数,其中n是加密码的块大小。
因此即使xor加密或按位反转也可以用于某些目的。
这是一个使用xor和bitwise reverse作为简单的1对1加密的php函数。
它是一个伪随机数生成器,可以保证填充所有值,并且没有相同的值。您提供n:0..63并且它提供随机0..63。
它只接受2 ^ i范围,如0..63,0..127等。
不是加密安全等,只是随机。
这样的函数非常适合垃圾收集例程,因为它不会尝试两次清理同一区域,即使是随机做事。
function math_random_filled($n,$bits,$seed)
{
//bits: examples: 6=0..63, 8=0..255, 10: 0..1023
//n: 0<= n <2^$bits
//seed: any string or number
//generate xor: crc32 + bit mask
$xor= crc32($seed.'internalseed') & ~(-1<<$bits);
//xor
$r=intval($n)^$xor;
//bitwise reverse
$r=bindec(strrev(str_pad(decbin($r),$bits,'0',STR_PAD_LEFT)));
return $r;
}
//demonstration
$bits=6;
$min=0;
$max=pow(2,$bits)-1;
$count=$max-$min+1;
for($n=0;$n<=$max;$n++)
{
$r=math_random_filled($n,$bits,$seed='someseed');
echo " $r ";
$ar[$r]=1;
}
$set=0;
for($n=0;$n<=$max;$n++)
if(isset($ar[$n])) $set++;
echo "\n"."bits: $bits, count: $count, set: ". $set."\n\n";
示例输出:
37 5 53 21 45 13 61 29 33 1 49 17 41 9 57 25 39 7 55 23 47 15 63 31 35 3 51 19 43 11 59 27 36 4 52 20 44 12 60 28 32 0 48 16 40 8 56 24 38 6 54 22 46 14 62 30 34 2 50 18 42 10 58 26
bits: 6, count: 64, set: 64
you can test the code here in php sandbox
这是另一个,但接受任何范围,而不仅仅是2的权力。
function math_random_filled_arithmetical($n,$max,$seed)
{
/*
- produces 0..max, repeatable, unique, one-to-one mapped random numbers
- uses arithmetic operations to imitate randomness
- $n: 0<=$n=<$max
- $max: any integer, not only power of two
- $seed: any string or number
*/
$n=intval($n);
$max=intval($max);
$opt1=$n;
$opt2=$max-$n;
$n2=min($opt1,$opt2);
$reverseit=crc32($seed.'internalseed'.$n2)&1;
if($opt1>$opt2) $reverseit=!$reverseit;
$max2=floor(intval($max-1)/2);
//echo "n:$n, max:$max,n2:$n2,max2:$max2,reverseit:$reverseit\n";
if($max>=3)
if($opt1!=$opt2)
$n2=math_random_filled_arithmetical($n2,$max2,$seed.'*');
$res=$reverseit? $max-$n2:$n2;
$res=intval(fmod($res+(crc32($seed)&(1<<30)),$max+1));
//echo "n:$n, max:$max, res:$res\n";
return $res;
}
//demonstration
$max=41;//-- test a max value
for($n=0;$n<=$max;$n++)
{
$r=math_random_filled_arithmetical($n,$max,$seed='someseed');
$ar[$r]=1;
echo " $r ";
}
$filled=0;
for($n=0;$n<=$max;$n++)
if(isset($ar[$n])) $filled++;
echo "\n count: ".($max+1).", filled: ". $filled."\n";
示例输出:
20 19 18 17 33 32 37 36 14 13 31 34 35 26 16 11 12 3 39 40 0 41 1 2 38 29 30 25 15 6 7 10 28 27 5 4 9 8 24 23 22 21
count: 42, filled: 42