如何在C#中保存Random Generator的状态?

时间:2013-10-22 07:58:53

标签: c# random

出于测试目的,我使用给定的种子创建随机数(即不基于当前时间)。

因此整个计划是确定性的。

如果发生了什么事情,我希望能够在事件发生前不久迅速恢复一点。

因此,我需要能够将System.Random恢复到以前的状态。

有没有办法提取种子,我可以用它来重建随机生成器?

7 个答案:

答案 0 :(得分:9)

answer given here一致,我写了一个小班来帮助保存和恢复状态。

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

您可以在LINQPad

中测试此代码

答案 1 :(得分:3)

这就是我的想法:

基本上它提取私有种子数组。 您只需要小心恢复“非共享”数组。

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

}

答案 2 :(得分:1)

System.Random未被密封且其方法是虚拟的,因此您可以创建一个类来计算为跟踪状态而生成的数字的数量,如:

class StateRandom : System.Random
{
    Int32 _numberOfInvokes;

    public Int32 NumberOfInvokes { get { return _numberOfInvokes; } }

    public StateRandom(int Seed, int forward = 0) : base(Seed)
    {
        for(int i = 0; i < forward; ++i)
            Next(0);
    }

    public override Int32 Next(Int32 maxValue)
    {
        _numberOfInvokes += 1;
        return base.Next(maxValue);
    }
}

使用示例:

void Main()
{
    var a = new StateRandom(123);
    a.Next(100);
    a.Next(100);
    a.Next(100);

    var state = a.NumberOfInvokes;
    Console.WriteLine(a.Next(100));
    Console.WriteLine(a.Next(100));
    Console.WriteLine(a.Next(100));

    // use 'state - 1' to be in the previous state instead
    var b = new StateRandom(123, state);
    Console.WriteLine(b.Next(100));
    Console.WriteLine(b.Next(100));
    Console.WriteLine(b.Next(100));

}

输出:

81
73
4
81
73
4

答案 3 :(得分:1)

我知道这个问题已经得到了解答,但是,我想提供我自己的实现,目前正用于我正在创建的游戏。基本上,我使用.NET的Random.cs代码创建了自己的Random类。我不仅添加了更多功能,而且还添加了一种方法来保存当前生成器状态并将其加载到仅有59个索引的数组中。最好这样做,而不是其他一些注释建议&#34;迭代x次以手动恢复状态。这是一个坏主意,因为在RNG重游戏中,你的随机生成器状态理论上可以进入数十亿次调用,这意味着你需要迭代10亿次以恢复每次启动时最后一次播放会话的状态。当然,这可能只需要一秒钟,但我认为它仍然太脏了,特别是当您可以简单地提取随机生成器的当前状态并在需要时重新加载它,并且只占用1个阵列(59个记忆指数)。

这只是一个想法,所以从我的代码中得到你想要的东西。

以下是完整的来源,这篇文章太大了,无法在此发布:

GrimoireRandom.cs

对于那些只想要实现问题的人,我会在这里发布。

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

除DiceType枚举和OpenTK Vector3结构外,我的代码完全独立。这两个功能都可以删除,它将适合您。

答案 4 :(得分:0)

有另一种解决方案,(1)避免需要记住所有先前生成的数字; (2)不涉及访问Random的私有字段; (3)不需要序列化; (4)不需要遍历Random的次数达到调用的次数;并且(5)不需要为内置的Random类创建替换。

诀窍是通过生成随机数来获取状态,然后将随机数生成器重新播种到该值。然后,将来,人们总是可以通过将随机数生成器重新设置为该值来返回此状态。换句话说,我们以随机数顺序“烧掉”一个数字以保存状态并重新播种。

实现如下。请注意,可以访问Generator属性实际生成数字。

public class RestorableRandom
{
    public Random Generator { get; private set; }

    public RestorableRandom()
    {
        Generator = new Random();
    }

    public RestorableRandom(int seed)
    {
        Generator = new Random(seed);
    }

    public int GetState()
    {
        int state = Generator.Next();
        Generator = new Random(state);
        return state;
    }

    public void RestoreState(int state)
    {
        Generator = new Random(state);
    }
}

这是一个简单的测试:

[Fact]
public void RestorableRandomWorks()
{
    RestorableRandom r = new RestorableRandom();
    double firstValueInSequence = r.Generator.NextDouble();
    int state = r.GetState();
    double secondValueInSequence = r.Generator.NextDouble();
    double thirdValueInSequence = r.Generator.NextDouble();
    r.RestoreState(state);
    r.Generator.NextDouble().Should().Be(secondValueInSequence);
    r.Generator.NextDouble().Should().Be(thirdValueInSequence);
}

答案 5 :(得分:-2)

这里是从此处的一些答案中摘录的精制版本,只需将其添加到您的项目中即可。

public class RandomState
    {

        private static Lazy<System.Reflection.FieldInfo> _seedArrayInfo = new Lazy<System.Reflection.FieldInfo>(typeof(System.Random).GetField("_seedArray", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static));
        private static Lazy<System.Reflection.FieldInfo> _inextInfo = new Lazy<System.Reflection.FieldInfo>(typeof(System.Random).GetField("_inext", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static));
        private static Lazy<System.Reflection.FieldInfo> _inextpInfo = new Lazy<System.Reflection.FieldInfo>(typeof(System.Random).GetField("_inextp", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static));
        private static System.Reflection.FieldInfo seedArrayInfo {get { return _seedArrayInfo.Value; }}
        private static System.Reflection.FieldInfo inextInfo { get { return _inextInfo.Value; } }
        private static System.Reflection.FieldInfo inextpInfo { get { return _inextpInfo.Value; } }

        private int[] seedState;
        private int inext;
        private int inextp;
        public static RandomState GetState(Random random)
        {
            var state = new RandomState() { seedState = ((int[])seedArrayInfo.GetValue(random)).ToArray(), inext = (int)inextInfo.GetValue(random), inextp = (int)inextpInfo.GetValue(random) };
            return state;
        }
        public static void SetState(Random random, RandomState state)
        {
            seedArrayInfo.SetValue(random, state.seedState.ToArray());
            inextInfo.SetValue(random, state.inext);
            inextpInfo.SetValue(random, state.inextp);
        }
    }
    public static class RandomExtensions
    {
        public static RandomState GetState (this System.Random random)
        {

            return RandomState.GetState(random);
        }
        public static void ApplyState (this System.Random random, RandomState state)
        {

            RandomState.SetState(random, state);
        }
    }

使用它尝试复制this的示例。

    public class Program
    {
        public static void Main (string[] args)
        {
            System.Random rnd = new System.Random (255);
            var firststate = rnd.GetState();
            Console.WriteLine("Saved initial state...");
            PrintRandom ("Step ", rnd);
            PrintRandom ("Step ", rnd);
            PrintRandom("Step ", rnd);
            var oldState = rnd.GetState();
            Console.WriteLine("Saved second state....");
            PrintRandom ("Step ", rnd);
            PrintRandom ("Step ", rnd);
            PrintRandom("Step ", rnd);
            PrintRandom("Step ", rnd);
            PrintRandom("Step ", rnd);
            rnd.ApplyState(oldState);
            Console.WriteLine("Re-applied second state state....");
            PrintRandom ("Step ", rnd);
            PrintRandom ("Step ", rnd);
            PrintRandom ("Step ", rnd);
            PrintRandom ("Step ", rnd);
            PrintRandom ("Step ", rnd);
            rnd.ApplyState(firststate);
            Console.WriteLine("Re-applied initial state state....");
            PrintRandom("Step ", rnd);
            PrintRandom ("Step ", rnd);
            PrintRandom ("Step ", rnd);
        }

        static void PrintRandom (string label, Random rnd)
        {
            System.Console.WriteLine(string.Format ("{0} - RandomValue {1}", label, rnd.Next (1, 100)));
        }
    }

输出:

Saved initial state...
Step  - RandomValue 94
Step  - RandomValue 64
Step  - RandomValue 1
Saved second state....
Step  - RandomValue 98
Step  - RandomValue 34
Step  - RandomValue 40
Step  - RandomValue 16
Step  - RandomValue 37
Re-applied second state state....
Step  - RandomValue 98
Step  - RandomValue 34
Step  - RandomValue 40
Step  - RandomValue 16
Step  - RandomValue 37
Re-applied initial state state....
Step  - RandomValue 94
Step  - RandomValue 64
Step  - RandomValue 1

答案 6 :(得分:-3)

存储随机数生成器运行的次数,如Xi Huan所写。

然后简单地循环以恢复旧状态。

Random rand= new Random();
int oldRNGState = 439394;

for(int i = 1; i < oldRNGState-1; i++) {
    rand.Next(1)
}

现在就做

int lastOldRNGValue = rand.Next(whateverValue);

没有办法解决这个问题,你必须循环回到你离开的地方。