我正在实施simulated annealing(SA)算法,我需要复制状态(例如,记住目前为止最好的解决方案)。
我实施了一种复制方法,因为它不鼓励使用java clone()
。
SA是一种启发式算法,因此下一步要随机确定。这是通过使用我想要复制的Random
对象来完成的。
虽然算法不需要,但我希望副本具有完全相同的状态。 但只有这样,如果我制作一份“复制品”。在创建对象后直接使用相同的种子初始化它。
但是如果我在复制过程之前对随机执行某些操作,则Random
对象的固有状态(即种子)会发生变化,并且复制的行为会有所不同。
那么如何获得java.util.Random
实例的精确副本?
示例
public class State
{
private final Random r;
private final long seed;
private Object currentOperand;
public State()
{
this(System.nanoTime(), null);
}
private State(long seed, Object currentOperand)
{
this.seed = seed;
this.r = new Random(seed);
this.currentOperand = currentOperand;
}
public State copy()
{
return new State(seed, currentOperand);
}
public void doSth()
{
/* operation with random operand */
currentOperand = r.nextInt(100);
}
public void redo()
{
// redo then set to null
currentOperand = null;
}
/* for completeness' sake... since it's simulated annealing */
public int computeEnergy() { return 0; }
}
答案 0 :(得分:2)
我提出了一个自己的解决方案。它主要覆盖next()
中的Random
(因为所有其他方法都依赖于那个),以及其他一些保持一致性的东西。
它提供了调用此方法的实例的精确副本(是否有必要使随机实例的副本成为另一个主题... ^^)。它应该像它的超级类似,至少这是我的意图。
随意添加您的想法!
由于其他问题是关于获取种子:可以轻松地在我的解决方案中添加getSeed()
方法。或getInitialSeed()
,getCurrentSeed()
。
/* Bounded parameter type since a class that implements this interface
* should only be able to create copies of the same type (or a subtype).
*/
public interface Copyable<T extends Copyable<T>>
{
public T copy();
}
public class CopyableRandom extends Random implements Copyable<CopyableRandom>
{
private final AtomicLong seed = new AtomicLong(0L);
private final static long multiplier = 0x5DEECE66DL;
private final static long addend = 0xBL;
private final static long mask = (1L << 48) - 1;
public CopyableRandom() { this(++seedUniquifier + System.nanoTime()); }
private static volatile long seedUniquifier = 8682522807148012L;
public CopyableRandom(long seed) { this.seed.set((seed ^ multiplier) & mask); }
/* copy of superclasses code, as you can seed the seed changes */
@Override
protected int next(int bits)
{
long oldseed, nextseed;
AtomicLong seed_ = this.seed;
do
{
oldseed = seed_.get();
nextseed = (oldseed * multiplier + addend) & mask;
} while (!seed_.compareAndSet(oldseed, nextseed));
return (int) (nextseed >>> (48 - bits));
}
/* necessary to prevent changes to seed that are made in constructor */
@Override
public CopyableRandom copy() { return new CopyableRandom((seed.get() ^ multiplier) & mask); }
public static void main(String[] args)
{
CopyableRandom cr = new CopyableRandom();
/* changes intern state of cr */
for (int i = 0; i < 10; i++)
System.out.println(cr.nextInt(50));
Random copy = cr.copy()
System.out.println("\nTEST: INTEGER\n");
for (int i = 0; i < 10; i++)
System.out.println("CR\t= " + cr.nextInt(50) + "\nCOPY\t= " + copy.nextInt(50) + "\n");
Random anotherCopy = (copy instanceof CopyableRandom) ? ((CopyableRandom) copy).copy() : new Random();
System.out.println("\nTEST: DOUBLE\n");
for (int i = 0; i < 10; i++)
System.out.println("CR\t= " + cr.nextDouble() + "\nA_COPY\t= " + anotherCopy.nextDouble() + "\n");
}
}
这里是主要方法的输出:
19
23
26
37
41
34
17
28
29
6
TEST: INTEGER
CR = 3
COPY = 3
CR = 18
COPY = 18
CR = 25
COPY = 25
CR = 9
COPY = 9
CR = 24
COPY = 24
CR = 5
COPY = 5
CR = 15
COPY = 15
CR = 5
COPY = 5
CR = 30
COPY = 30
CR = 26
COPY = 26
TEST: DOUBLE
CR = 0.7161924830704971
A_COPY = 0.7161924830704971
CR = 0.06333509362539957
A_COPY = 0.06333509362539957
CR = 0.6340753697524675
A_COPY = 0.6340753697524675
CR = 0.13546677259518425
A_COPY = 0.13546677259518425
CR = 0.37133033932410586
A_COPY = 0.37133033932410586
CR = 0.796277965335522
A_COPY = 0.796277965335522
CR = 0.8610310118615391
A_COPY = 0.8610310118615391
CR = 0.793617231340077
A_COPY = 0.793617231340077
CR = 0.3454111197621874
A_COPY = 0.3454111197621874
CR = 0.25314618087856255
A_COPY = 0.25314618087856255
我还有一个测试,我将CopyableRandom与Random进行了比较。它产生了相同的结果。
long seed = System.nanoTime();
Random cr = new CopyableRandom(seed);
Random cmp = new Random(seed);
答案 1 :(得分:2)
我知道这是一个老生常谈的问题,答案是可以接受的,但是我在寻找答案时碰到了这个问题,因此我采取了另一种方法。
看到上面的Random
实现Serializable
的迈克笔记,我只是用它来制作副本:
/**
* Uses serialization to create a copy of the given Random, needed for
* repeatability in some tests.
*/
public static Random cloneRandom(Random src) throws Exception {
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bo);
oos.writeObject(src);
oos.close();
ObjectInputStream ois = new ObjectInputStream(
new ByteArrayInputStream(bo.toByteArray()));
return (Random)(ois.readObject());
}
它的性能可能不及Mike的CopyableRandom
,但要简单得多,并且足以进行我的小单元测试。
(我有一个现有的单元测试,该测试以具有已知种子的Random
开头,然后执行了一系列操作;我试图在测试的中间中添加一些内容测试,并想要一份Random
的副本;在测试过程中调用nextLong()
或类似方法以获取/ a种子将要更改种子,从而炸毁了其余的测试。我只想要Random.getSeed()
之类的东西。)
答案 2 :(得分:1)
我认为你应该存储在你的State
课程中,不仅要开始种子,还要调用你已经完成的nextInt()
来电。这是由于Random
生成伪随机数字序列的内在事实。那就是:
可以使用种子状态从任意开始状态开始伪随机数生成器。在使用该状态初始化时,它将始终产生相同的序列
让我先解释一下示例:
public static void main(String[] args){
Random r = new Random(42);
Random s = new Random(42);
for(int i = 0; i < 5; i++){
System.out.println("First random " +r.nextInt());
}
for(int i = 0; i < 5; i++){
System.out.println("Second random " +s.nextInt());
}
}
结果是:
First random -1170105035
First random 234785527
First random -1360544799
First random 205897768
First random 1325939940
Second random -1170105035
Second random 234785527
Second random -1360544799
Second random 205897768
Second random 1325939940
由于两个Random实例都以相同的种子开头,因此我总是得到相同的数字序列。
因此,在复制对象时,您应该将新的Random
初始化为相同的源种子(您已经这样做了),然后“使用”尽可能多地调用nextInt()
你已经在源对象中使用过的(这就是你必须保留这个数字的原因)。
完成此操作后,调用副本上的nextInt()
将获得与源对象相同的结果。我希望我的代码足以重构你的代码并让你理解我的解决方案。
要了解更好的伪随机数生成器(PRNG),也称为确定性随机位生成器(DRBG),您可以查看此Wikipedia article