Swift 4.2+种子随机数生成器

euoag5mw  于 2023-05-16  发布在  Swift
关注(0)|答案(6)|浏览(245)

我试图用Swift 4.2+生成种子随机数,使用Int.random()函数,但是没有给定的实现允许随机数生成器被种子化。据我所知,唯一的方法是创建一个符合RandomNumberGenerator协议的新随机数生成器。有没有人有一个更好的方法来做这件事的建议,或者一个具有种子功能的RandomNumberGenerator符合类的实现,以及如何实现它?
另外,在我寻找解决方案时,我看到两个函数sranddrand被提到过几次,但从它被提到的次数来看,我不确定使用它是否是一个糟糕的约定,而且我也找不到关于它们的任何文档。
我正在寻找最简单的解决方案,不一定是最安全或最快的性能(例如,使用外部库将不是理想的)。

**更新:**通过“种子”,我的意思是我将种子传递给随机数生成器,这样如果我将同一种子传递给两个不同的设备或在两个不同的时间,生成器将产生相同的数字。这样做的目的是随机生成应用程序的数据,而不是将所有数据保存到数据库中,我希望保存种子,并在用户每次加载应用程序时使用该种子重新生成数据。

vltsax25

vltsax251#

所以我使用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()用于Int、Bool、Double等),这给了我所需的灵活性和简单实现。

vmpqdwk3

vmpqdwk32#

以下是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
}
vm0i2vca

vm0i2vca3#

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)
// when deployed used seed: Int.random(in: 0..<Int.max)

然后使用它:

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

fquxozlt4#

Swift的RandomNumberGenerator.next(using:)changed in 2019实现这会影响Collection.randomElement(using:),并导致它总是返回第一个元素,如果您的生成器的next()->UInt64实现没有在UInt64域中均匀地生成值。因此,这里提供的GKRandom解决方案是有问题的,因为它的next->Int方法声明:

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

这里有一个解决方案,我在Swift的TensorFlow中使用RNG找到了here

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

感谢我的同事塞缪尔、诺亚和斯蒂芬,他们帮助我弄清了这件事。

cczfrluj

cczfrluj5#

我最终使用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"
eyh26e7m

eyh26e7m6#

当我尝试使用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 Cookpost,它具有一个公共领域算法的Swift实现,在我上面的测试中看起来更随机(不存在明显的模式)

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

相关问题