如何在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