Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to save the state of a Random generator in C#?

Tags:

c#

random

For testing purposes I'm creating random numbers with a given seed (i.e. not based on the current time).

Thus the whole program is deterministic.

If something happens, I'd like to be able to quickly restore a point "shortly before" the incident.

Therefore I need to be able to restore a System.Random to a previous state.

Is there a way to extract a seed which I can use to recreate the random generator?

like image 956
Onur Avatar asked Oct 22 '13 07:10

Onur


3 Answers

In line with the answer given here, I wrote a small class to help with saving and restoring the state.

void Main()
{
    var r = new Random();

    Enumerable.Range(1, 5).Select(idx => r.Next()).Dump("before save");
    var s = r.Save();
    Enumerable.Range(1, 5).Select(idx => r.Next()).Dump("after save");
    r = s.Restore();
    Enumerable.Range(1, 5).Select(idx => r.Next()).Dump("after restore");

    s.Dump();
}

public static class RandomExtensions
{
    public static RandomState Save(this Random random)
    {
        var binaryFormatter = new BinaryFormatter();
        using (var temp = new MemoryStream())
        {
            binaryFormatter.Serialize(temp, random);
            return new RandomState(temp.ToArray());
        }
    }

    public static Random Restore(this RandomState state)
    {
        var binaryFormatter = new BinaryFormatter();
        using (var temp = new MemoryStream(state.State))
        {
            return (Random)binaryFormatter.Deserialize(temp);
        }
    }
}

public struct RandomState
{
    public readonly byte[] State;
    public RandomState(byte[] state)
    {
        State = state;
    }
}

You can test this code in LINQPad.

like image 185
Lasse V. Karlsen Avatar answered Oct 27 '22 21:10

Lasse V. Karlsen


This is what I came up:

Basically it extracts the private seed array. You just need to be careful to restore an "unshared" array.

var first = new Random(100);

// gain access to private seed array of Random
var seedArrayInfo = typeof(Random).GetField("SeedArray", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var seedArray = seedArrayInfo.GetValue(first) as int[];

var other = new Random(200); // seed doesn't matter!

var seedArrayCopy = seedArray.ToArray(); // we need to copy since otherwise they share the array!

seedArrayInfo.SetValue(other, seedArrayCopy);


for (var i = 10; i < 1000; ++i)
{
    var v1 = first.Next(i);
    var v2 = other.Next(i);

    Debug.Assert(v1 == v2);

}
like image 7
Onur Avatar answered Oct 27 '22 19:10

Onur


I'm aware this question has already been answered, however, I wanted to provide my own implementation, which is currently in use for a game that I am creating. Essentially, I created my own Random class, using the code of .NET's Random.cs. Not only did I add more functionality, but I also added a way to save and load the current generator state into and from an array of just 59 indices. It is better to do it this way instead of how some other comments suggest to "Iterate x number of times to restore the state manually. This is a bad idea because in RNG heavy games your Random generator state could theoretically get into the billions of calls, meaning you would—according to them—need to iterate a billion times to restore the state of the last play session during each startup. Granted, this may still only take a second, tops, but it's still too dirty in my opinion, especially when you could simply extract the current state of the Random Generator and reload it when required, and only taking up 1 array (59 indices of memory).

This is just an idea, so take from my code what you will.

Here is the full source, which is much too large to post here:

GrimoireRandom.cs

And for anyone who just wants the implementation for the question, I will post it here.

        public int[] GetState()
        {
            int[] state = new int[59];
            state[0] = _seed;
            state[1] = _inext;
            state[2] = _inextp;
            for (int i = 3; i < this._seedArray.Length; i++)
            {
                state[i] = _seedArray[i - 3];
            }
            return state;
        }

        public void LoadState(int[] saveState)
        {
            if (saveState.Length != 59)
            {
                throw new Exception("GrimoireRandom state was corrupted!");
            }
            _seed = saveState[0];
            _inext = saveState[1];
            _inextp = saveState[2];
            _seedArray = new int[59];
            for (int i = 3; i < this._seedArray.Length; i++)
            {
                _seedArray[i - 3] = saveState[i];
            }
        }

My code is completely stand-alone, besides the DiceType enumeration, and the OpenTK Vector3 struct. Both of those functions can just be deleted and it will work for you.

like image 4
Krythic Avatar answered Oct 27 '22 21:10

Krythic