Перестановка массива (используя: RandomNumberGenerator) странное поведение - PullRequest
2 голосов
/ 30 марта 2020

Начиная с Xcode 11.4, массив вообще не перетасовывается (независимо от начального числа) при использовании второй реализации функции next.

Я не понимаю почему, поскольку обе функции генерируют случайные числа, хотя первое заполняет только младшие 32 бита случайного Int64.

Вы можете попробовать этот минимальный пример в Swift Playgrounds, комментируя одну из двух функций.

    import GameplayKit

    struct SeededGenerator: RandomNumberGenerator {
        static var shared: SeededGenerator?

        let seed: UInt64
        let generator: GKMersenneTwisterRandomSource

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

        // New alternative found to be working
        mutating func next() -> UInt64 {
            let next1 = UInt64(bitPattern: Int64(generator.nextInt()))
            let next2 = UInt64(bitPattern: Int64(generator.nextInt()))
            return next1 | (next2 << 32)
        }

        // Code previously in use that doesn't work anymore.
        mutating func next() -> UInt64 {
            return UInt64(bitPattern: Int64(abs(generator.nextInt())))
        }
    }

    var gen = SeededGenerator(seed: 234)

    var array = ["1", "2", "3"]
    array.shuffle(using: &gen)

1 Ответ

2 голосов
/ 30 марта 2020

Проблема в том, что метод nextInt() для всех типов GKRandom возвращает целочисленное значение в диапазоне [INT32_MIN, INT32_MAX], что означает, что ваша «нерабочая» реализация next() возвращает 64-битные значения с старшими 32 битами, равными нулю. Это «нарушает» требование протокола RandomNumberGenerator, при котором вызовы next() должны давать равномерно распределенные 64-битные значения.

В более старых выпусках Swift это могло не вызывать проблем, но с реализацией из почти случайного целочисленного генерирования Lemire на 64-битных платформах Intel это приводит к тому, что Random.next(upperBound:) всегда возвращает ноль:

var gen = SeededGenerator(seed: 234)
print((0..<20).map { _ in Int.random(in: 0..<10, using: &gen) })
// [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

Как следствие, метод shuffle() вообще не меняет местами элементы массива.

Ваша альтернативная реализация next() работает, потому что она заполняет как младшие, так и старшие 32 бита 64-битного случайного числа.

...