整数到随机数的稳定映射

时间:2014-07-10 09:58:37

标签: java random prng

我需要稳定快速单向映射整数到随机数的函数。 通过"稳定"我的意思是相同的整数应始终映射到相同的随机数。 并通过"随机数"我实际上是指"某些数字表现得像随机"。

e.g。

1 -> 329423
2 -> -12398791234
3 -> -984
4 -> 42342435
...

如果我有足够的记忆(和时间),我最好使用:

for( int i=Integer.MIN_VALUE; i<Integer.MAX_VALUE; i++ ){
    map[i]=i;
}
shuffle( map );

我可以使用一些像MD5或SHA这样的安全哈希函数,但这些功能对我的目的来说很慢,而且我不需要任何加密/安全属性。

我只需要一种方式。所以我永远不必将随机数转换回整数。

背景:(对于那些想要了解更多信息的人)

我计划在给定的时间内使用它来使完整缓存无效。失效完成&#34;随机&#34;随着时间的推移,访问缓存成员的机会增加。我需要这个是稳定的,因此isValid(条目)不会&#34;闪烁&#34;并进行一致的测试。 该函数的输入将是条目的密钥的java哈希,其通常在&#34; 1000&#34; - &#34; 15000&#34;的范围内。 (但也可以包含其他一些东西)并且有大量的东西。 失效是在以下条件下完成的:

elapsedTime / timeout * Integer.MAX_VALUE > abs( random( key.hashCode() ) )

编辑:(这是为了评论,所以我把它放在这里)

我尝试了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 );

        }

输出以:

开头
00000, 00000030, bac2c591
00001, 00000031, babce6a4
00002, 00000032, bace836b
00003, 00000033, bac8a47e
00004, 00000034, baab49de
00005, 00000035, baa56af1
00006, 00000036, bab707b7
00007, 00000037, bab128ca
00008, 00000038, ba93ce2a
00009, 00000039, ba8def3d
00010, 0000061f, 98048199

以这种方式继续下去。

我可以使用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 );
        }

确实看起来很随机,但不再稳定慢10倍比上面的方法。

3 个答案:

答案 0 :(得分:4)

使用Random并将其与您的号码一起播种:

Random generator = new Random(i);
return generator.nextInt();

当你的测试暴露时,这种方法的问题是这样的种子在第一次迭代中创建了一个非常差的随机数。为了提高结果的质量,我们需要运行几次随机发生器;这将填充具有伪随机值的随机生成器的状态,并将提高以下值的质量。

要确保随机生成器足够分散值,请在输出数字前使用几次。这应该使得结果数字更加伪随机:

Random generator = new Random(i);
for(int i = 0; i < 5; i++) generator.nextInt();
return generator.nextInt();

尝试不同的值,可能5就足够了。

答案 1 :(得分:2)

gexicide的答案是正确的(也是最简单的)。只需一个注意事项:

在我的系统上运行1,000,000次约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);
}

multiplieraddendmaskRandom使用的常量

运行此1,000,000次可获得相同的结果,但只需 5ms ,因此快10倍

BTW:这恰好是来自 The Old Man 的另一段代码 - 再次。见唐纳德克努特, 计算机程序设计的艺术,第2卷,第3.2.1节

答案 2 :(得分:2)

虽然您从未将其指定为要求,但您可能需要完整的1:1映射。这是因为可能的输入值的数量很小。对于多个输入可能发生的任何输出意味着另一个输出,它根本不会发生。如果您的输出值不可能,那么您的分布会有偏差。

当然,如果您的输入有偏差,那么您的输出仍会偏斜,而且您无能为力。

反正;这使它成为unique int to int hash

只需应用几个简单,独立的1:1映射函数,直到适当分布。你已经从Random类中隔离了一个变换,但我建议将它与其他变换(如shift和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;
}

如果这足够好,那么你可以通过随机删除行来加快速度(我建议你保持至少一个乘法),直到它不再足够好,然后再添加最后删除的行。