Java 整数到随机数的稳定映射
我需要一个从整数到随机数的稳定的和快速的单向映射函数。 所谓“稳定”,我的意思是相同的整数应该总是映射到相同的随机数。 我所说的“随机数”实际上是指“行为类似随机数的数” e、 g 如果我有足够的内存(和时间),我会理想地使用:Java 整数到随机数的稳定映射,java,random,prng,Java,Random,Prng,我需要一个从整数到随机数的稳定的和快速的单向映射函数。 所谓“稳定”,我的意思是相同的整数应该总是映射到相同的随机数。 我所说的“随机数”实际上是指“行为类似随机数的数” e、 g 如果我有足够的内存(和时间),我会理想地使用: for( int i=Integer.MIN_VALUE; i<Integer.MAX_VALUE; i++ ){ map[i]=i; } shuffle( map ); 编辑:(这是一个很长的评论,所以我把它放在这里) 我试过gexicide的答案,结
for( int i=Integer.MIN_VALUE; i<Integer.MAX_VALUE; i++ ){
map[i]=i;
}
shuffle( map );
编辑:(这是一个很长的评论,所以我把它放在这里)
我试过gexicide的答案,结果发现这还不够随机。以下是我尝试过的:
for( int i=0; i<12000; i++ ){
int hash = (""+i).hashCode();
Random rng = new Random( hash );
int random = rng.nextInt();
System.out.printf( "%05d, %08x, %08x\n", i, hash, random );
}
就这样继续下去
我可以使用SecureRandom
:
for( int i=0; i<12000; i++ ){
SecureRandom rng = new SecureRandom( (""+i).getBytes() );
int random = rng.nextInt();
System.out.printf( "%05d, %08x\n", i, random );
}
for(int i=0;i使用一个Random
并用您的号码作为种子:
Random generator = new Random(i);
return generator.nextInt();
正如您的测试所揭示的,这种方法的问题是,这样一个种子在第一次迭代中创建了一个非常差的随机数。为了提高结果的质量,我们需要运行随机生成器几次;这将用伪随机值填充随机生成器的状态,并将提高以下v的质量价值观
为确保随机生成器足够分散这些值,请在输出数字之前使用它几次。这将使生成的数字更为伪随机:
Random generator = new Random(i);
for(int i = 0; i < 5; i++) generator.nextInt();
return generator.nextInt();
Random generator=new Random(i);
对于(inti=0;i<5;i++)生成器.nextInt();
返回生成器.nextInt();
尝试不同的值,也许5
就足够了。gexicide的答案是正确的(也是最简单的)。请注意:
在我的系统上运行1000000次需要大约70ms(相当快)
但它至少涉及两个对象创建,并向GC提供数据。这样会更好
如果这可以在堆栈上完成,而不使用对象创建
查看Random
类的源代码,可以发现有一些代码需要编写
它可以多次调用,并使其线程安全,可以删除
因此,我最终用一种方法重新实现:
public static int mapInteger( int value ){
// initial scramble
long seed = (value ^ multiplier) & mask;
// shuffle three times. This is like calling rng.nextInt() 3 times
seed = (seed * multiplier + addend) & mask;
seed = (seed * multiplier + addend) & mask;
seed = (seed * multiplier + addend) & mask;
// fit size
return (int)(seed >>> 16);
}
(乘法器
、加数
和掩码
是随机
使用的一些常量)
运行1000000次会得到相同的结果,但只需5ms,因此速度10倍
顺便说一句:这恰好是老人的另一段代码——再次。见Donald Knuth,
计算机编程艺术,第2卷,第3.2.1节尽管您从未将其指定为要求,但您可能需要完整的1:1映射。这是因为可能输入值的数量很小。对于多个输入,任何可能发生的输出都意味着另一个根本不可能发生的输出。如果您有输出值w这是不可能的,那么你有一个偏态分布
当然,如果你的输入有偏差,那么你的输出也会有偏差,对此你无能为力
不管怎么说,这使它成为了一个好消息
只需应用两个简单、独立的1:1映射函数,直到事情得到适当的分布。您已经从Random类中分离出一个变换,但我建议将它与其他变换(如移位和XOR)混合,以避免不同算法的个别弱点
例如:
public static int mapInteger( int value ){
value *= 1664525;
value += 1013904223;
value ^= value >>> 12;
value ^= value << 25;
value ^= value >>> 27;
value *= 1103515245;
value += 12345;
return value;
}
public静态int-mapInteger(int值){
值*=1664525;
数值+=1013904223;
值^=值>>>12;
值^=值>>27;
值*=1103515245;
数值+=12345;
返回值;
}
如果这足够好,那么您可以通过随机删除行来加快速度(我建议您至少保留一个乘法)直到它不再足够好,然后再添加最后删除的行。您可以使用带有固定种子的Random
的shuffle。这将每次给您相同的随机顺序。只需使用固定种子初始化PRNG,并使用它提供的第n个随机数,或者如果它提供唯一的值,则可能使用@gexicide的解决方案下面。但我对应用程序提出了疑问。不难记住最近使用最少或最不常用的缓存项,并在每次迭代中删除它。您将需要一个PRNG(伪随机数生成器)实现并每次使用相同的种子启动它,然后从中构建序列。随机数在映射中是否需要唯一?(例如,它是否是所有整数值到所有整数值的随机映射?)不。它不需要唯一。(但如果是的话也不会很糟。)快几秒钟。:)+1@EJP:没有,它们不一定是独一无二的。但这不是OP的要求。谢谢你的回答,但这不起作用。请参阅上面的“我的编辑”。@Scheintod:尝试我的编辑,即尝试调用几次nextInt()
,以“预热”所创建的生成器。测试可能的迭代次数。事实证明,就我的目的而言,只需要一个额外的shuffle/nextInt。而且还是很快。谢谢你的想法。正如我所理解的(我对模数学不太了解)1:1映射,每个操作都需要可逆。我可以看出+=
是如何可逆的。但是当被乘数为奇数时,*=
和^=>
是可逆的,因为int的范围是2的幂,所有奇数都是2的幂的共素数。现在恐怕我想不出一个简单的方法来证明这一点,我也不知道要真正扭转局面有多容易(我只知道每个结果都是独一无二的)<代码>^=>
是可逆的,因为^
是可逆的。嘿,谢谢!对于乘法,我已经找到了:它提到了欧几里德算法。但还有一件事:它不应该是value^=value>>12
等等吗。?根据你的解释,我认为>
也是可逆的,但我认为其目的是用零填充左侧?对不起,是的,你说得很对<代码>>是act
Random generator = new Random(i);
for(int i = 0; i < 5; i++) generator.nextInt();
return generator.nextInt();
public static int mapInteger( int value ){
// initial scramble
long seed = (value ^ multiplier) & mask;
// shuffle three times. This is like calling rng.nextInt() 3 times
seed = (seed * multiplier + addend) & mask;
seed = (seed * multiplier + addend) & mask;
seed = (seed * multiplier + addend) & mask;
// fit size
return (int)(seed >>> 16);
}
public static int mapInteger( int value ){
value *= 1664525;
value += 1013904223;
value ^= value >>> 12;
value ^= value << 25;
value ^= value >>> 27;
value *= 1103515245;
value += 12345;
return value;
}