Search code examples
javaalgorithmrandomcopyclone

Copy instance variable of type java.util.Random to create object in same state


I'm implementing a simulated annealing (SA) algorithm, where I need to copy states (e. g. to remember best solution so far).

I implemented a copy method, since it's discouraged to use java's clone().

SA is a heuristic algorithm, so the next step to take is determined randomly. This is done by using a Random object, which I want to copy too.

Although it's not requiered by the algorithm, I want the copy to have exactly the same state. But this is only the case, if I make a 'copy' direct after object creation and initialize it with the same seed.

But if I perform some operations on the random before the copy process , the intrinsic state (i. e. the seed) of theRandom object changes and the copy behaves differently.

So how can I get an exact copy of an instance of java.util.Random?


EXAMPLE

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

Solution

  • I came up with an own solution. It mainly overrides next() in Random (since all other methods rely on that one), and some other stuff to keep the consistency.

    It delivers an exact copy of the instance this method was invoked on (whether it makes sense to make a copy of a random instance is another topic...^^). It should exactly behave like its super class, at least that was my intention.

    Feel free to add your thoughts!

    Since other questions were about getting the seed: One could easily add a getSeed() method to my solution. Or 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");
      }
    }
    

    And here the output of the main method:

    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
    

    I also had a test where I compared CopyableRandom against Random. It yielded the same results.

    long seed = System.nanoTime();
    
    Random cr  = new CopyableRandom(seed);
    Random cmp = new Random(seed);