Swift 4.2+播种随机数生成器

RPa*_*l99 5 random random-seed swift swift4.2

我正在尝试使用Swift 4.2+通过Int.random()函数生成带种子的随机数,但是没有给定的实现允许带给随机数生成器的实现。据我所知,唯一的方法就是创建一个新的符合RandomNumberGenerator协议的随机数生成器。是否有人建议采用更好的方法,或者是否有符合RandomNumberGenerator的类且具有播种功能的实现,以及如何实现?

另外,在寻找解决方案时,我看到了两个函数sranddrand提到了几次,但是从提到它的频率来看,我不确定使用它是否不好,而且我也找不到关于它们的任何文档。

我正在寻找最简单的解决方案,不一定是最安全或最快的性能解决方案(例如,使用外部库并不理想)。

更新: “播种”是指我将种子传递给随机数生成器,以便如果我将同一种子传递给两个不同的设备或在两个不同的时间传递,则生成器将产生相同的数字。目的是我为应用程序随机生成数据,而不是将所有数据保存到数据库中,而是想保存种子,并在每次用户加载应用程序时使用该种子重新生成数据。

RPa*_*l99 14

所以我使用 Martin R 的建议使用GamePlayKit'sGKMersenneTwisterRandomSource来创建一个符合 RandomNumberGenerator 协议的类,我能够使用 in 函数的一个实例,例如Int.random()

import GameplayKit

class SeededGenerator: RandomNumberGenerator {
    let seed: UInt64
    private let generator: GKMersenneTwisterRandomSource
    convenience init() {
        self.init(seed: 0)
    }
    init(seed: UInt64) {
        self.seed = seed
        generator = GKMersenneTwisterRandomSource(seed: seed)
    }
    func next<T>(upperBound: T) -> T where T : FixedWidthInteger, T : UnsignedInteger {
        return T(abs(generator.nextInt(upperBound: Int(upperBound))))
    }
    func next<T>() -> T where T : FixedWidthInteger, T : UnsignedInteger {
        return T(abs(generator.nextInt()))
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

// Make a random seed and store in a database
let seed = UInt64.random(in: UInt64.min ... UInt64.max)
var generator = Generator(seed: seed)
// Or if you just need the seeding ability for testing,
// var generator = Generator()
// uses a default seed of 0

let chars = ['a','b','c','d','e','f']
let randomChar = chars.randomElement(using: &generator)
let randomInt = Int.random(in: 0 ..< 1000, using: &generator)
// etc.
Run Code Online (Sandbox Code Playgroud)

通过结合GKMersenneTwisterRandomSource标准库的随机函数(如.randomElement()数组和.random()Int、Bool、Double 等)的播种功能和简单性,这为我提供了所需的灵活性和简单实现。


Gri*_*tin 13

这是 RPatel99 的答案的替代方案,该答案考虑了 GKRandom 值范围。

import GameKit

struct ArbitraryRandomNumberGenerator : RandomNumberGenerator {

    mutating func next() -> UInt64 {
        // GKRandom produces values in [INT32_MIN, INT32_MAX] range; hence we need two numbers to produce 64-bit value.
        let next1 = UInt64(bitPattern: Int64(gkrandom.nextInt()))
        let next2 = UInt64(bitPattern: Int64(gkrandom.nextInt()))
        return next1 ^ (next2 << 32)
    }

    init(seed: UInt64) {
        self.gkrandom = GKMersenneTwisterRandomSource(seed: seed)
    }

    private let gkrandom: GKRandom
}
Run Code Online (Sandbox Code Playgroud)

  • @DavidMoles我用小测试检查了它,是的,有一半的时间我在上部看到 0xffffffff 和 `next1 | (下一个 &lt;&lt; 32)`。使用“next1 ^ (next2 &lt;&lt; 32)”它看起来是随机的。正在更新答案,谢谢! (4认同)

mrz*_*zmr 8

Swift 5 的简化版本:

struct RandomNumberGeneratorWithSeed: RandomNumberGenerator {
    init(seed: Int) { srand48(seed) }
    func next() -> UInt64 { return UInt64(drand48() * Double(UInt64.max)) }
}
@State var seededGenerator = RandomNumberGeneratorWithSeed(seed: 123)
// use fixed seed for testing, when deployed use Int.random(in: 0..<Int.max)
Run Code Online (Sandbox Code Playgroud)

然后使用它:

let rand0to99 = Int.random(in: 0..<100, using: &seededGenerator)
Run Code Online (Sandbox Code Playgroud)