Java 整数到随机数的稳定映射

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的答案,结

我需要一个从整数到随机数的稳定的快速的单向映射函数。 所谓“稳定”,我的意思是相同的整数应该总是映射到相同的随机数。 我所说的“随机数”实际上是指“行为类似随机数的数”

e、 g

如果我有足够的内存(和时间),我会理想地使用:

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