如何在C#中保存随机生成器的状态?

如何在C#中保存随机生成器的状态?,c#,random,C#,Random,出于测试目的,我使用给定的种子创建随机数(即,不基于当前时间) 因此,整个程序是确定的 如果发生了什么事情,我希望能够快速恢复事件“前不久”的一个点 因此,我需要能够将System.Random恢复到以前的状态 有没有一种方法可以提取一个种子,我可以用它来重新创建随机生成器?系统。random不是密封的,它的方法是虚拟的,因此您可以创建一个类来计算生成的数字的数量,以跟踪状态,例如: class StateRandom : System.Random { Int32 _numberOfI

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

因此,整个程序是确定的

如果发生了什么事情,我希望能够快速恢复事件“前不久”的一个点

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


有没有一种方法可以提取一个种子,我可以用它来重新创建随机生成器?

系统。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);
    }
}
输出:

81
73
4
81
73
4
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

存储随机数生成器像
Xi Huan
写入那样运行的次数

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

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

for(int i = 1; i < oldRNGState-1; i++) {
    rand.Next(1)
}
没有办法解决这个问题,您必须循环回到您停止的地方。

根据,我编写了一个小类来帮助保存和恢复状态

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

您可以在中测试此代码。

这是我的想法:

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

var first=新随机数(100);
//获得对随机数的私有种子数组的访问权
var seedArrayInfo=typeof(Random).GetField(“SeedArray”,System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var seedArray=seedArrayInfo.GetValue(第一个)为int[];
var other=新随机数(200);//种子不重要!
var seedArrayCopy=seedArray.ToArray();//我们需要复制,否则他们将共享阵列!
seedArrayInfo.SetValue(其他,seedArrayCopy);
对于(变量i=10;i<1000;++i)
{
var v1=第一个。下一个(i);
var v2=其他。下一个(i);
断言(v1==v2);
}

我知道这个问题已经得到了回答,但是,我想提供我自己的实现,它目前正在用于我正在创建的游戏。本质上,我使用.NET的Random.cs代码创建了自己的Random类。我不仅添加了更多的功能,而且还添加了一种将当前生成器状态保存和加载到只包含59个索引的数组中的方法。最好是这样做,而不是像其他评论所建议的那样“迭代x次以手动恢复状态。这是个坏主意,因为在RNG密集型游戏中,从理论上讲,随机生成器状态可能会进入数十亿次调用,这意味着您需要根据调用次数迭代十亿次,以恢复每次启动期间最后一次播放会话的状态。当然,这可能只需要一秒钟,但在我看来,它仍然太脏了,特别是当您可以提取随机生成器的当前状态并在需要时重新加载它,并且只占用1个数组(59个内存索引)时

这只是一个想法,所以从我的代码中你会得到什么

这是完整的来源,太大了,无法在这里发布:

对于任何想要实现这个问题的人,我会把它贴在这里

        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];
            }
        }
public int[]GetState()
{
int[]状态=新int[59];
状态[0]=\u种子;
状态[1]=\u不正确;
状态[2]=\u inxtp;
对于(int i=3;i

除了DiceType枚举和OpenTK Vector3结构之外,我的代码是完全独立的。这两个函数都可以删除,它将为您工作。

有一个替代解决方案:(1)无需记住以前生成的所有数字;(2) 不涉及访问随机变量的私有字段;(3) 不需要序列化;(4) 不需要像调用它那样多次通过随机循环返回;和(5)不需要为内置随机类创建替换

诀窍是通过生成一个随机数来获取状态,然后将随机数生成器重新播种到此值。然后,在将来,通过将随机数生成器重新设定为该值,始终可以返回到该状态。换句话说,为了保存状态和重新播种,我们在随机数序列中“燃烧”一个数字

执行情况如下。请注意,可以访问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);
}

这是一个从这里的一些答案中提取的精练版本,只需将其添加到您的项目中即可

公共类状态
{
private static Lazy | u seedArrayInfo=new Lazy(typeof(System.Random).GetField(_seedArray),BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.static));
private static Lazy | u inextInfo=new Lazy(typeof(System.Random).GetField(_inext),BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.static));
private static Lazy _inextpInfo=new Lazy(typeof(System.Random).GetField(_inextp),BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.static));
私有静态System.Reflection.FieldInfo seedArrayInfo{get{return}u seedArrayInfo.Value;}
私有静态System.Reflection.FieldInfo infintinfo{get{return\u infintinfo.Value;}}
私有静态System.Reflection.FieldInfo inxtpinfo{get{return\u i
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);
}
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