下面的说明,这是我目前的代码。
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次计算,即使它在系统上并不是很难。如果可能,为什么不进行优化?
答案 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 = 0x5DEECE66DL
,b = 0xBL
和m = 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)
简单的回答是:不,它是一个随机函数,它生成的伪随机数是前一次执行的结果,因此只有在应用相同的方式时才会产生相同的结果。
但是您的实施还有一些替代方案。但在优化之前,您应该验证是否确实需要优化。因为这对我来说有点像过早的优化。那你为什么更喜欢这种方法进行优化呢?您是否已经确定了要解决的性能瓶颈?它多久被调用一次?优化的权衡取舍是什么?等。
因此,让我们假设你有充分的理由来优化这种方法,这里有一些选择:
另一种方法是保留以前的Random
个实例
在地图中生成数字并从Random
开始计算索引,该索引是所请求数字的次要最小值。例如
map.put(10, temp)
map.put(5,temp)
Random temp = map.remove(5)
,计算为7,map.put(7,temp)
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()。