Swift 4.2 播种一个随机数生成器



我试图用swift 4.2 生成带有 Int.random()函数的播种随机数,但是没有给定的实现,可以允许将随机数生成器播种。据我所知,这样做的唯一方法是创建一个符合RandomNumberGenerator协议的新的随机数生成器。是否有人建议这样做更好的方法,或者实现了具有种子的功能以及如何实施的随机名称符合类别的类别?

另外,我已经看到两个函数sranddrand在我寻找解决方案时几次提到了几次,但是从很少提及的情况下来看,我不确定是否使用它是不好的惯例,我也找不到它们的任何文档。

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

更新:通过"种子",我的意思是我要将种子传递到随机数生成器,以便如果我将同一种子传递到两个不同的设备或在两个不同的时间,发电机将产生相同的数字。目的是我要随机生成一个应用程序数据,而不是将所有数据保存到数据库中,而是要保存种子并每次用户加载应用程序时使用该种子再生数据。

因此,我使用Martin R的建议使用GamePlayKitGKMersenneTwisterRandomSource来制作符合RandomNumberGenerator协议的类,我可以在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()))
    }
}

用法:

// 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.

这使我通过组合GKMersenneTwisterRandomSource的播种功能和标准库的随机函数的简单性(例如.randomElement()的数组和 .random() for int,bool,double等),这为我提供了我需要的灵活性和轻松实现。>

这是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
}

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)

然后使用它:

let rand0to99 = Int.random(in: 0..<100, using: &seededGenerator)

看起来Swift在2019年更改了RandomNumberGenerator.next(using:)的实现。这会影响Collection.randomElement(using:),并导致它始终返回第一个元素,如果您的生成器的next()->UInt64实现未在UInt64的整个域中均匀产生值。因此,此处提供的GKRandom解决方案是有问题的,因为它是next->Int方法:

     * The value is in the range of [INT32_MIN, INT32_MAX].

这是一个解决方案,使用Swift的TensorFlow中的RNG在此处找到:


public struct ARC4RandomNumberGenerator: RandomNumberGenerator {
  var state: [UInt8] = Array(0...255)
  var iPos: UInt8 = 0
  var jPos: UInt8 = 0
  /// Initialize ARC4RandomNumberGenerator using an array of UInt8. The array
  /// must have length between 1 and 256 inclusive.
  public init(seed: [UInt8]) {
    precondition(seed.count > 0, "Length of seed must be positive")
    precondition(seed.count <= 256, "Length of seed must be at most 256")
    var j: UInt8 = 0
    for i: UInt8 in 0...255 {
      j &+= S(i) &+ seed[Int(i) % seed.count]
      swapAt(i, j)
    }
  }
  // Produce the next random UInt64 from the stream, and advance the internal
  // state.
  public mutating func next() -> UInt64 {
    var result: UInt64 = 0
    for _ in 0..<UInt64.bitWidth / UInt8.bitWidth {
      result <<= UInt8.bitWidth
      result += UInt64(nextByte())
    }
    print(result)
    return result
  }
  // Helper to access the state.
  private func S(_ index: UInt8) -> UInt8 {
    return state[Int(index)]
  }
  // Helper to swap elements of the state.
  private mutating func swapAt(_ i: UInt8, _ j: UInt8) {
    state.swapAt(Int(i), Int(j))
  }
  // Generates the next byte in the keystream.
  private mutating func nextByte() -> UInt8 {
    iPos &+= 1
    jPos &+= S(iPos)
    swapAt(iPos, jPos)
    return S(S(iPos) &+ S(jPos))
  }
}

给我的同事塞缪尔,诺亚和斯蒂芬的提示,他们帮助我陷入了困境。

我最终使用 srand48()drand48()来生成一个带有种子的伪随机编号。

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

因此,在生产中,实现使用SystemRandomNumberGenerator,但在测试套件中使用SeededRandomNumberGenerator

示例:

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 order
randomMessageData() //"a string"
randomMessageData() //["some", ["values": 456]]
randomMessageData() //["some", ["values": 456]]
randomMessageData() //["some", ["values": 456]]
randomMessageData() //"a string"

当我尝试使用Bool.random(using:)尝试时,srand48实现对我不起作用。他们生产:

var randomNumberGenerator = RandomNumberGeneratorWithSeed(seed:69)
for _ in 0..<100 {
  print("(Bool.random(using: &randomNumberGenerator))")
}
true
true
false
false
true
true
false
false
true
true
...

但是,在Swift论坛中,我找到了Nate Cook的帖子,其迅速实现了公共领域算法,在上面的测试中看起来更随机(不存在明显的模式)

// This is a fixed-increment version of Java 8's SplittableRandom generator.
// It is a very fast generator passing BigCrush, with 64 bits of state.
// See http://dx.doi.org/10.1145/2714064.2660195 and
// http://docs.oracle.com/javase/8/docs/api/java/util/SplittableRandom.html
//
// Derived from public domain C implementation by Sebastiano Vigna
// See http://xoshiro.di.unimi.it/splitmix64.c
public struct SplitMix64: RandomNumberGenerator {
    private var state: UInt64
    public init(seed: UInt64) {
        self.state = seed
    }
    public mutating func next() -> UInt64 {
        self.state &+= 0x9e3779b97f4a7c15
        var z: UInt64 = self.state
        z = (z ^ (z &>> 30)) &* 0xbf58476d1ce4e5b9
        z = (z ^ (z &>> 27)) &* 0x94d049bb133111eb
        return z ^ (z &>> 31)
    }
}

最新更新