Swift 4.2+ seeding a random number generator Swift 4.2+ seeding a random number generator swift swift

Swift 4.2+ seeding a random number generator


So I used Martin R's suggestion to use GamePlayKit's GKMersenneTwisterRandomSource to make a class that conformed to the RandomNumberGenerator protocol, which I was able to use an instance of in functions like Int.random():

import GameplayKitclass 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()))    }}

Usage:

// Make a random seed and store in a databaselet 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 0let chars = ['a','b','c','d','e','f']let randomChar = chars.randomElement(using: &generator)let randomInt = Int.random(in: 0 ..< 1000, using: &generator)// etc.

This gave me the flexibility and easy implementation that I needed by combining the seeding functionality of GKMersenneTwisterRandomSource and the simplicity of the standard library's random functions (like .randomElement() for arrays and .random() for Int, Bool, Double, etc.)


Here's alternative to the answer from RPatel99 that accounts GKRandom values range.

import GameKitstruct 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}


I ended up using srand48() and drand48() to generate a pseudo-random number with a seed for a specific test.

class SeededRandomNumberGenerator : RandomNumberGenerator {    let range: ClosedRange<Double> = Double(UInt64.min) ... Double(UInt64.max)    init(seed: Int) {        // srand48() — Pseudo-random number initializer        srand48(seed)    }    func next() -> UInt64 {        // drand48() — Pseudo-random number generator        return UInt64(range.lowerBound + (range.upperBound - range.lowerBound) * drand48())    }    }

So, in production the implementation uses the SystemRandomNumberGenerator but in the test suite it uses the SeededRandomNumberGenerator.

Example:

let messageFixtures: [Any] = [    "a string",    ["some", ["values": 456]],]var seededRandomNumberGenerator = SeededRandomNumberGenerator(seed: 13)func randomMessageData() -> Any {    return messageFixtures.randomElement(using: &seededRandomNumberGenerator)!}// Always return the same element in the same orderrandomMessageData() //"a string"randomMessageData() //["some", ["values": 456]]randomMessageData() //["some", ["values": 456]]randomMessageData() //["some", ["values": 456]]randomMessageData() //"a string"