英文:
Save random state in golang
问题
在golang的math/rand包中,有没有一种方法可以保存随机状态?
我想将其序列化并存储以供以后使用,但是随机状态是一个接口,接口下面的具体结构是未导出的(所以似乎不能使用json.Marshall)。
作为保存rand.Source对象的替代方法,我考虑只保存底层的int64种子值。你可以使用rand.Seed来设置它,但是我没有找到一种获取种子值的方法,以便以后使用。
英文:
Is there a way to save the random state in the golang math/rand package?
I would like to serialize it and store it for later use, but the random state is an interface and the concrete struct underneath the interface is unexported (so json.Marshall apparently cannot be used).
As an alternative to saving the rand.Source object, I thought about just saving the underlying int64 seed value. You can set it with rand.Seed, but I don't see a way to obtain the seed value so that it can be retained for later use.
答案1
得分: 2
正如你已经注意到的,math/rand
包无法提供关于(伪)随机数生成器的当前状态(种子)的信息。如果你需要这个信息,你需要自己实现(正如匿名用户所提到的)。
假设你知道随机数生成器的内部状态等同于设置种子值为1234
的情况。那么这种情况比其他具体或“随机”数作为种子更好在哪里呢?
下面是一个“模拟”访问生成器种子值的提示:
假设你已经创建了一个Rand
对象,并且已经设置并使用了它(已经生成了一些随机数)。这个Rand
对象可以是math/rand
包的默认/全局对象,不一定要是一个独立的Rand
对象。
当你达到一个希望保存随机数生成器的“状态”,以便以后可以从这个点开始重复生成完全相同的伪随机序列时,你需要获取当前的种子值。然后,当你想要从这个点开始重复序列时,你只需将存储的种子设置给Rand
对象即可。问题是:你无法访问种子值。
但是你可以设置一个新的种子,然后你就知道内部种子就是你刚刚设置的那个!所以为了模拟GetSeed()
(在math/rand
包的默认Rand
对象上):
func GetSeed() int64 {
seed := time.Now().UnixNano() // 一个新的随机种子(与状态无关)
rand.Seed(seed)
return seed
}
模拟GetSeed()
或任何Rand
对象(不是默认的):
func GetSeed2(r rand.Rand) int64 {
seed := time.Now().UnixNano() // 一个新的随机种子(与状态无关)
r.Seed(seed)
return seed
}
保留“伪”随机性
上述提出的GetSeed()
使用当前时间来“重新种子化”Rand
对象。这将根据调用时的时间改变伪随机序列。这可能是可以接受的,也可能不可以(在大多数情况下,这不是一个问题)。
但是,如果需要避免这种情况,我们可以使用Rand
对象本身来指定新的种子,像这样(通过这样做,新的种子将仅依赖于当前状态-当前种子):
func GetSeed() int64 {
seed := rand.Int63() // 一个新的伪随机种子:由当前状态/种子决定
rand.Seed(seed)
return seed
}
func GetSeed2(r rand.Rand) int64 {
seed := r.Int63() // 一个新的伪随机种子:由当前状态/种子决定
r.Seed(seed)
return seed
}
注意:
GetSeed()
功能设计为偶尔使用,例如当你想保存一个游戏时。正常使用情况下,会生成成千上万个随机数,并且只调用一次GetSeed()
。
正如匿名用户指出的,在当前伪随机数生成器的算法中,在某些极端情况下(例如在每个生成的随机数后调用GetSeed()
),这可能导致生成的随机数循环,并返回先前生成的随机数序列。序列的长度可能在几千左右。这里有一个例子,序列长度为8034:重复Go Playground示例。
但是再次强调:这不是正常的使用方式,GetSeed()
是有意在每个随机数生成后调用的;而且这仅适用于使用Rand
本身来生成新种子的情况。如果你使用当前时间重新种子化你的Rand
对象,重复是不会发生的。
英文:
As you already noted, the math/rand
package does not give you insight into the current state (seed) of the (pseudo-)random number generator. If you would need this, you would have to implement it on your own (as mentioned by Anonymous).
If you would: Ok, let's say you know that the internal state of the random number generator is equivalent to where a seed value of 1234
would be set. How is this better than if the seed is any other concrete or "random" number?
Here's a tip how to "simulate" access to the seed value of the generator:
Let's say you've already created your Rand
object, you've set it up and used it (already generated some random numbers). This Rand
object may as well be the default/global of the math/rand
package, it doesn't have to be a distinct Rand
.
You reach a point where you would like to save the "sate" of the random number generator in order so that later you can repeat the exact pseudo-random sequence from this point. This would need to get the current seed, and later when you want to repeat the sequence from this point, you would just set the stored seed to the Rand
object. Problem: the seed you can't access.
But what you can do is set a new seed and then you know that the internal seed is the one you just set! So to simulate a GetSeed()
(on the default Rand
of the math/rand
package):
func GetSeed() int64 {
seed := time.Now().UnixNano() // A new random seed (independent from state)
rand.Seed(seed)
return seed
}
To simulate a GetSeed()
or any Rand
(not the default one):
func GetSeed2(r rand.Rand) int64 {
seed := time.Now().UnixNano() // A new random seed (independent from state)
r.Seed(seed)
return seed
}
Preserving the "pseudo" part of the randomness
The above proposed GetSeed()
uses the current time to "re-seed" the Rand
object. This will alter the pseudo-random sequence based on the time it was called at. This may or may not be ok (in most of the times this is not a problem).
But if it is, we can avoid this if we use the Rand
object itself to designate the new seed like this (by doing this the new seed will only depend on the current state - the current seed):
func GetSeed() int64 {
seed := rand.Int63() // A new pseudo-random seed: determined by current state/seed
rand.Seed(seed)
return seed
}
func GetSeed2(r rand.Rand) int64 {
seed := r.Int63() // A new pseudo-random seed: determined by current state/seed
r.Seed(seed)
return seed
}
Notes:
The GetSeed()
functionality is designed to used occasionally, e.g. when you want to save a game. Normal usage would be to generate thousands of random numbers and call GetSeed()
once only.
As Anonymous pointed out, with the current algorithm of the pseudo-random number generator, in some extreme situations (for example you call GetSeed()
after each generated random number) this may result in a cycle in the generated random numbers and would return the previously generated sequence of random numbers. The length of the sequence may be around a couple of thousands. Here is an example of this where the length of the sequence is 8034: Repetition Go Playground Example.
But again: this is not a normal usage, GetSeed()
is called intentionally after each random number generation; and this only applies if you use Rand
itself to generate the new seed. If you re-seed your Rand
object with the current time for example, repetition will not occur.
答案2
得分: 1
你可以创建自己的随机数源,并进行编组。
一种方法是简单地从 http://golang.org/src/math/rand/rng.go 复制代码,并通过添加能够以你想要的方式编组状态的代码进行调整。这将为你提供自己的 rand.Source
,你可以在 rand.New(myRandSource)
中使用它来生成随机数。
英文:
You can make your own random number source which can be marshalled.
One way would be to simply copy the code from http://golang.org/src/math/rand/rng.go and adapt it by adding code that can marshal the state in whatever way you want. That'll give you your own rand.Source
that you can use in rand.New(myRandSource)
to generate random numbers.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论