17

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

因此整个程序是确定性的。

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

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

有没有办法提取可以用来重新创建随机生成器的种子?

4

7 回答 7

14

根据这里给出的答案,我写了一个小类来帮助保存和恢复状态。

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中测试此代码。

于 2013-10-22T08:39:11.107 回答
7

这就是我想出的:

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

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

}
于 2013-10-22T08:45:57.427 回答
4

我知道这个问题已经得到解答,但是,我想提供我自己的实现,该实现目前正在用于我正在创建的游戏。本质上,我使用 .NET 的 Random.cs 的代码创建了自己的 Random 类。我不仅添加了更多功能,而且还添加了一种方法来保存当前生成器状态并将其加载到仅有 59 个索引的数组中。最好这样做,而不是其他一些评论建议“迭代 x 次以手动恢复状态。这是一个坏主意,因为在 RNG 重游戏中,您的随机生成器状态理论上可能会进入数十亿次调用,这意味着你会——根据他们的说法——需要迭代十亿次才能在每次启动期间恢复上一次播放会话的状态。当然,这可能仍然只需要一秒钟,顶,但它

这只是一个想法,所以从我的代码中获取你想要的。

这是完整的源代码,太大了,无法在此处发布:

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 结构之外,我的代码是完全独立的。这两个功能都可以删除,它会为你工作。

于 2017-09-15T14:13:07.500 回答
3

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
于 2013-10-22T08:08:15.640 回答
2

有一个替代解决方案(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);
}
于 2018-07-22T13:57:07.713 回答
-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);
        }
    }

使用它的示例,试图复制

    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
于 2020-12-09T12:18:32.860 回答
-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);

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

于 2013-10-22T08:28:33.390 回答