Java随机(种子)在索引处随机获取?

时间:2016-04-25 19:41:51

标签: java random

下面的说明,这是我目前的代码。

Random seeded = new Random(seed);

    for(int j = 0; j < 3; j++)
    {
        for (int i = 0; i < 10; i++)
            System.out.println(i + " : " + seeded.nextInt(100));
        System.out.println("{}");
        seeded= new Random(seed);
    }

输出以下内容

0 : 91
1 : 76
2 : 3
3 : 56
4 : 92
5 : 98
6 : 92
7 : 46
8 : 74
9 : 60
{}
0 : 91
1 : 76
2 : 3
3 : 56
4 : 92
5 : 98
6 : 92
7 : 46
8 : 74
9 : 60
{}
0 : 91
1 : 76
2 : 3
3 : 56
4 : 92
5 : 98
6 : 92
7 : 46
8 : 74
9 : 60
{}

我的目标正是如上所述。我想创建一个扩展Random的类,包括一个getSeededIntAtIndex函数。例如,我想按如下方式实现该类。

IndexedRandom random = new IndexedRandom(seed);
int iAt30 = random.getIntAtIndex(30);

目前,为此,我有以下代码。

public int getIntAtIndex (int index)
{
    Random temp = new Random(seed);
    for(int i = 0; i < index - 1; i++)
        temp.nextInt();

    return temp.nextInt();
}

我只是在随机X次循环直到达到所需的数字。但是,如果我选择一个大型索引,比如3,000,000,我宁愿优化这种方法。有什么办法可以优化吗? 这种方法只需约0.34秒即可在任何设备,PC或手机上运行。我只问了这个问题,因为它似乎浪费了2,999,999次计算,即使它在系统上并不是很难。如果可能,为什么不进行优化?

2 个答案:

答案 0 :(得分:4)

java.util.Random specified使用以下重复来推进种子:

(seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1)

这是linear congruential generator,因此we can advance it N steps in only O(log(N)) time

允许a = 0x5DEECE66DLb = 0xBLm = 1L << 48。 N步之后的种子值具有以下封闭形式(非实际Java ):

new_seed = (a^N * seed mod m) + (b * ((a^N - 1) mod (a-1)m)/(a-1) mod m)

我没有尝试将其写为有效的Java语句有两个原因。第一个是我们需要模幂运算来有效地评估它,第二个是我们需要80位算术来计算(a^N - 1) mod (a-1)m。这两个问题都可以使用Java BigInteger class解决,它提供modular exponentiation和任意精度的整数。或者,我们可以使用模幂运算算法的变量在64位算术的范围内将((a^N - 1) mod (a-1)m)/(a-1)计算为(a^(N-1) + a^(N-2) + ... + a + 1) mod m,从而使我们不必经历BigInteger的开销。

Nayuki,我之前联系过的人之一,有一个working demo的LCG,能够根据BigInteger向前和向后跳过。您可以将其与为java.util.Random指定的参数一起使用,以实现具有前导功能的java.util.Random等效项,或者您可以自己编写。

答案 1 :(得分:1)

简单的回答是:不,它是一个随机函数,它生成的伪随机数是前一次执行的结果,因此只有在应用相同的方式时才会产生相同的结果。

但是您的实施还有一些替代方案。但在优化之前,您应该验证是否确实需要优化。因为这对我来说有点像过早的优化。那你为什么更喜欢这种方法进行优化呢?您是否已经确定了要解决的性能瓶颈?它多久被调用一次?优化的权衡取舍是什么?等。

因此,让我们假设你有充分的理由来优化这种方法,这里有一些选择:

  • 根据您的内存限制,您可以预先填充一组完整的随机数并将其保留在内存中。这需要大约12 MB。这可能是速度最有效的方式。如果经常请求较低的值,您可以预先计算前1000个值并将它们保存在内存中,并将计算机上的所有内容保存在内存中。如果要求低于1000的值比高值更频繁,则此方法很好。
  • 如果您从未生成3M值,但3M仅定义值范围,则另一种方法是将已计算的值保留在地图中,以便您可以查找它们而不是重新计算它们。如果请求的索引是相当随机的,但是同一索引多次请求相同值的概率很高,这种方法会很好。
  • 另一种方法是保留以前的Random个实例 在地图中生成数字并从Random开始计算索引,该索引是所请求数字的次要最小值。例如

    • getIntAtIndex(10) - &gt;没有随机索引&lt; 10,计算10,map.put(10, temp)
    • getIntAtIndex(5) - &gt;没有随机索引&lt; 5,计算5,map.put(5,temp)
    • getIntAtIndex(7) - &gt; Random temp = map.remove(5),计算为7,map.put(7,temp)
    • getIntAtIndex(12) - &gt; Random temp = map.remove(10),计算为12,map.put(12,temp)

但是我不会感到惊讶,如果这种方法比重新计算慢,特别是对于较小的索引,因为你必须在每个请求上搜索键。

实际上,Random的实现基于基本运算符,因此编译器应该相当高效和可优化。因此,在采取任何行动之前,我强烈建议分析是否确实存在瓶颈。

修改

我使用索引和迭代次数的各种组合运行测试。结果是,优化是您应该做的最小化。在我5岁的Core Duo上花了100多秒来计算100&#39000指数的值。执行时间随着指数的增加而线性增加,影响远大于预期。

这使得上面列出的所有优化都完全过时了。

假设用例是,您希望给定输入值的可重现但伪随机值在程序执行时不同,但在运行时保持不变。

所以我建议采用不同的方法。使用索引值作为种子,并通过更改计算结果的迭代次数来添加randomnes。以下代码段将为100个程序执行生成随机序列。如果您需要更多随机数,请更改用于计算&#34; seed&#34;的模数,但请注意,执行时间以线性方式增加。所以也许100就够了。

private long seed = System.currentTimeMillis() % 100;
public int getIntAtIndex (int index)
{
    Random temp = new Random(index);
    for(int i = 0; i < seed; i++)
        temp.nextInt();
    return temp.nextInt();
}

如果是游戏,则应使用给定的种子初始化随机数,然后通过重复使用相同的随机对象计算所需的所有值来计算&#34;启动参数&#34;你的游戏即如果你需要100个int参数,请调用100次nextInt()。