12
votes

Swift 4.2+ ensemence un générateur de nombres aléatoires

J'essaie de générer des nombres aléatoires prédéfinis avec Swift 4.2+, avec la fonction Int.random() , mais il n'y a pas d'implémentation donnée qui permet au générateur de nombres aléatoires d'être amorcé. Pour autant que je sache, la seule façon de le faire est de créer un nouveau générateur de nombres aléatoires conforme au protocole RandomNumberGenerator . Quelqu'un a-t-il une recommandation pour une meilleure façon de le faire, ou une implémentation d'une classe conforme RandomNumberGenerator qui a la fonctionnalité d'être amorcée, et comment l'implémenter?

De plus, je l' ai vu deux fonctions srand et drand mentionné une ou deux fois pendant que je cherchais une solution à cela, mais à en juger par la façon dont rarement il a été mentionné, je ne sais pas si vous l' utilisez est mauvaise convention, et je peux aussi » t trouver de documentation sur eux.

Je recherche la solution la plus simple, pas nécessairement la plus sûre ou la plus performante (par exemple, l'utilisation d'une bibliothèque externe ne serait pas idéale).

Mise à jour: Par "seeded", je veux dire que je devais passer une graine au générateur de nombres aléatoires pour que si je passe la même graine à deux appareils différents ou à deux moments différents, le générateur produira les mêmes nombres. Le but est que je génère au hasard des données pour une application, et plutôt que de sauvegarder toutes ces données dans une base de données, je souhaite enregistrer la graine et régénérer les données avec cette graine chaque fois que l'utilisateur charge l'application.


8 commentaires

Peut-être que cela vous aiderait si vous nous disiez ce que vous voulez accomplir, le mot seed / seeded semble être interprété assez différemment sur le net.


Si vous utilisez SystemRandomNumberGenerator, l'amorçage se fait automatiquement pour vous: developer.apple.com/documentation/swift/...


RandomNumberGenerator -vous utiliser RandomNumberGenerator ? GamePlayKit possède divers générateurs de nombres aléatoires, voir par exemple stackoverflow.com/a/53355215 .


@JoakimDanielson J'ai mis à jour la question


@AndreasOetjen J'ai besoin de pouvoir passer dans la graine selon mes propres conditions, voir la mise à jour dans la question.


@MartinR Je préfère ne pas importer de packages, mais s'il n'y a pas d'autre option, je vais certainement regarder GamePlayKit.


+1 sur la suggestion de Martin R d'utiliser un générateur de nombres aléatoires GamePlaykit. Ils ont des générateurs déterministes pour permettre aux nombres générés d'être reproductibles. MartinR devrait mettre à jour ce commentaire en réponse. Je me souviens d'une vidéo de la WWDC sur le gameplaykit mentionnant cette fonctionnalité.


Est-il possible de semer le générateur de nombres aléatoires Swift 4.2?


4 Réponses :


13
votes

Alors je Martin R suggestion de l'utilisation GamePlayKit de GKMersenneTwisterRandomSource pour faire une classe qui était conforme au protocole RandomNumberGenerator, que j'ai pu utiliser une instance de fonctions comme Int.random() :

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

Usage:

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

Cela m'a donné la flexibilité et l'implémentation facile dont j'avais besoin en combinant la fonctionnalité d' GKMersenneTwisterRandomSource de GKMersenneTwisterRandomSource et la simplicité des fonctions aléatoires de la bibliothèque standard (comme .randomElement() pour les tableaux et .random() pour Int, Bool, Double, etc.)


5 commentaires

Je me demande s'il y a un problème avec la plage des valeurs produites avec le générateur ci-dessus, étant donné que GKRandom (et par conséquent GKMersenneTwisterRandomSource) est documenté pour produire des valeurs dans la plage [INT32_MIN, INT32_MAX]. Veuillez voir ci-dessous pour une variante de comptabilité alternative.


Ce que @GrigoryEntin a dit. Même sur les plates-formes 64 bits, l'utilisation d' abs() vous limite à la moitié de la plage, donc par exemple, next<UInt64>() ne retournera jamais rien dans la plage (Int64.max + 1)...(UInt64.max) . Et je soupçonne que sur les plates-formes 64 bits, la next<UInt32>() produira beaucoup d'erreurs de débordement.


Étrange. J'ai essayé ce code et j'obtiens une boucle infinie si j'essaye quelque chose comme Double.random(in: 0...1, using: &gen) . Cela se produit sur mon iPhone et mon Mac. Il appelle next plusieurs reprises mais ne revient jamais pour donner le Double aléatoire.


@RobN, avez-vous résolu le problème? Je fas le même problème


@Tobias Ça fait un moment, mais oui je pense. Essayez quelque chose comme la réponse de Grigory Entin, où vous ne remplacez que la méthode next() -> UInt64 du protocole. Je ne sais pas pourquoi cette réponse essaie de remplacer les méthodes d'extension génériques.



10
votes

Voici une alternative à la réponse de RPatel99 qui prend en compte la plage de valeurs 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
}


4 commentaires

J'ai essayé cela, mais next(upperBound:) m'a donné des distributions étranges jusqu'à ce que je masque next1 et next2 avec & 0xFFFF_FFFF pour ne garder que les bits les moins significatifs.


(Je suppose que GKMersenneTwisterRandomSource renvoie la plage Int64 signée Int64 sur les plates-formes 64 bits?)


@DavidMoles Je l'ai vérifié avec GKMersenneTwisterRandomSource sur macOS. Il ne me donne rien au-dessus de 0x7FFF_FFFF (comptabilise le signe), donc il ne semble pas qu'il soit 64 bits. En ce qui concerne le masquage avec 0xFFFF_FFFF - je pense que cela a du sens, car "la moitié du temps" next1 serait généré à partir d'un Int32 négatif et cela signifie qu'il aurait tous les 32 bits élevés, et next1 | (next2 << 32) ne changerait pas ces bits hauts (c'est-à-dire que le nombre généré aurait 0xFFFF_FFFF au 32 bits haut). Une alternative serait probablement d'utiliser 'xor' au lieu de 'ou' dans next1 ^ (next2 << 32) .


@DavidMoles Je l'ai vérifié avec un petit test, oui, la moitié du temps, je vois 0xffffffff dans la partie supérieure avec next1 | (next2 << 32) . Avec next1 ^ (next2 << 32) cela semble aléatoire. Je vais mettre à jour la réponse, merci!



-5
votes
var g = SystemRandomNumberGenerator()

lazy private var random1 = Double.random(in: 0..<1, using: &self.g)
Ref:https://developer.apple.com/documentation/swift/systemrandomnumbergenerator

1 commentaires

Je n'ai pas voté contre votre réponse, mais je pense que quiconque l'a fait, probablement parce que vous n'avez pas fourni de moyen de semer le générateur de nombres aléatoires, ce que la question demande.



0
votes

On dirait que l'implémentation par Swift de RandomNumberGenerator.next(using:) changé en 2019 . Cela affecte Collection.randomElement(using:) et l'oblige à toujours renvoyer le premier élément si l'implémentation next()->UInt64 votre générateur ne produit pas de valeurs uniformément dans le domaine de UInt64 . La solution GKRandom fournie ici est donc problématique car c'est la méthode suivante- next->Int :

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

Voici une solution qui fonctionne pour moi en utilisant le RNG dans TensorFlow de Swift trouvé ici :

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

Pointe du chapeau à mes collègues Samuel, Noah et Stephen qui m'ont aidé à aller au fond des choses.


0 commentaires