【问题标题】:Random Number generation with GameplayKit使用 GameplayKit 生成随机数
【发布时间】:2025-12-16 14:40:02
【问题描述】:

我正在学习使用 swift 和 Gameplaykit 生成随机数。在下面的代码中,是在我初始化 randomDist 时生成的数字,它只是在我调用 nextInt 时从已经生成的数字中给出一个样本,还是在我调用 nextInt() 时随机生成器是惰性生成的?

let rand = GKMersenneTwisterRandomSource()          // the generator can be specified
let randomDist = GKRandomDistribution(randomSource: rand, lowestValue: 50, highestValue: 100)
randomDist.nextInt()

【问题讨论】:

    标签: swift gameplay-kit


    【解决方案1】:

    请记住,GKRandomDistribution 可以利用任何底层随机发生器——也就是说,不仅是任何 GameplayKit GKRandomSource 类,还包括任何实现 GKRandom 协议的类。因此,您可以通过实现自己的随机源并查看其方法被调用的方式/时间来自己回答这个问题。

    class LoggingRandomSource: GKRandom {
        let source = GKARC4RandomSource()
        @objc func nextInt() -> Int {
            let num = source.nextInt()
            print("nextInt: \(num)")
            return num
        }
        @objc func nextIntWithUpperBound(upperBound: Int) -> Int {
            let num = source.nextIntWithUpperBound(upperBound)
            print("nextIntWithUpperBound: \(num)")
            return num
        }
        @objc func nextUniform() -> Float {
            let num = source.nextUniform()
            print("nextUniform: \(num)")
            return num
        }
        @objc func nextBool() -> Bool {
            let flip = source.nextBool()
            print("nextBool: \(flip)")
            return flip
        }
    }
    
    let rand = LoggingRandomSource()
    let randomDist = GKRandomDistribution(randomSource: rand, lowestValue: 50, highestValue: 100)
    randomDist.nextInt()
    

    继续探索这个技巧,你会注意到一些关于随机分布类的事情:

    • GKRandomDistribution 对底层随机源调用一次nextIntWithUpperBound,每次调用其方法之一。这是有道理的,因为假设底层源的nextIntWithUpperBound 是统一的,所以GKRandomDistribution 需要做的就是将该统一的int 映射到它的lowestValue-highestValue 范围。

    • GKGaussianDistribution 对底层nextUniform 进行两次调用,每次调用其方法之一。这是因为在给定两个均匀随机值的情况下,有很多方法可以生成高斯(也称为正态)随机值——请参阅Box-Muller transform

    • GKShuffledDistribution 在您第一次向底层 nextIntWithUpperBound 请求一个数字时会进行大量调用,但您可以在不再次调用底层源的情况下向它请求更多。这符合该分布的记录行为:它确保在再次重复相同的值之前用尽其范围内的所有可能值。一种简单的方法是获取所有这些值,打乱它们的顺序,然后在每次调用时从打乱的池中提取一个值,直到它清空。

    【讨论】: