Choisissez un élément aléatoire dans un tableau

190

Supposons que j'ai un tableau et que je veuille choisir un élément au hasard.

Quelle serait la manière la plus simple de procéder?

La manière la plus évidente serait array[random index]. Mais peut-être qu'il y a quelque chose comme le ruby array.sample? Ou sinon, une telle méthode pourrait-elle être créée en utilisant une extension?

Fela Winkelmolen
la source
1
Avez-vous déjà essayé des méthodes différentes?
ford prefect
J'essaierais array[random number from 0 to length-1], mais je ne trouve pas comment générer un int aléatoire en swift, je le demanderais en cas de débordement de pile si je n'étais pas bloqué :) Je ne voulais pas polluer la question avec des demi-solutions alors qu'il y en a peut-être quelque chose comme ruby'sarray.sample
Fela Winkelmolen
1
Vous utilisez arc4random () comme vous le feriez dans Obj-C
Arbitur
Aucune explication de la raison pour laquelle votre question n'a pas reçu les mêmes commentaires que l'homologue JQuery. Mais en général, vous devez suivre ces directives lorsque vous postez une question. Comment poser une bonne question? . Donnez l'impression que vous avez fait un petit effort pour trouver une solution avant de demander de l'aide à quelqu'un d'autre. Lorsque je google "choisir un nombre aléatoire rapide", la première page est remplie de réponses suggérant arc4random_uniform. Aussi, RTFD ... "lisez la documentation f'ing". Il est surprenant de voir combien de questions peuvent être répondues de cette façon.
Austin A
Merci pour vos aimables commentaires. Oui, j'imagine que j'aurais dû répondre moi-même à la question, mais cela me semblait assez facile pour que ce soit bien de donner à quelqu'un d'autre les points de réputation presque gratuits. Et je l'ai écrit alors que même les documents officiels Apple Swift n'étaient pas publics, il n'y avait définitivement aucun résultat Google à ce moment-là. Mais la question était une fois à -12, donc je suis assez confiant qu'elle sera finalement positive :)
Fela Winkelmolen

Réponses:

327

Swift 4.2 et supérieur

La nouvelle approche recommandée est une méthode intégrée du protocole Collection: randomElement(). Il renvoie un optionnel pour éviter le cas vide que j'ai supposé précédemment.

let array = ["Frodo", "Sam", "Wise", "Gamgee"]
print(array.randomElement()!) // Using ! knowing I have array.count > 0

Si vous ne créez pas le tableau et que le nombre n'est pas garanti> 0, vous devez faire quelque chose comme:

if let randomElement = array.randomElement() { 
    print(randomElement)
}

Swift 4.1 et inférieur

Juste pour répondre à votre question, vous pouvez le faire pour obtenir une sélection aléatoire de tableaux:

let array = ["Frodo", "sam", "wise", "gamgee"]
let randomIndex = Int(arc4random_uniform(UInt32(array.count)))
print(array[randomIndex])

Les moulages sont laids, mais je pense qu'ils sont nécessaires à moins que quelqu'un d'autre ne le fasse autrement.

Lucas Derraugh
la source
4
Pourquoi Swift n'offre-t-il pas un générateur de nombres aléatoires qui renvoie un Int? Cette 2ème ligne semble très verbeuse juste pour renvoyer des Int choisis au hasard. Y a-t-il un avantage informatique / syntaxique à renvoyer un UInt32 par rapport à un Int? Aussi, pourquoi Swift n'offre-t-il pas une alternative Int à cette fonction ou ne permet-il pas à un utilisateur de spécifier le type d'entier qu'il aimerait retourner?
Austin A
Pour ajouter une note, cette méthode de générateur de nombres aléatoires pourrait empêcher la "polarisation modulo". Reportez man arc4random- vous et stackoverflow.com/questions/10984974/…
Kent Liau
1
@AustinA, Swift 4.2 A une fonction de générateur de nombres aléatoires native qui est implémentée sur tous les types de données scalaires que vous pourriez souhaiter attendre: Int, Double, Float, UInt32, etc. Et il vous permet de fournir des plages cibles pour les valeurs. Très utile. Vous pouvez utiliser array [Int.random (0 .. <array.count)] `dans Swift 4.2
Duncan C
Je souhaite que Swift 4.2 implémente une removeRandomElement()fonction en plus de randomElement(). Il serait modelé sur removeFirst(), mais supprimera un objet à un index aléatoire.
Duncan C
@DuncanC Vous devriez éviter 0..<array.count(pour plusieurs raisons, les principales étant que cela ne fonctionne pas pour les tranches, et qu'il est sujet aux erreurs). Vous pouvez faire let randomIndex = array.indices.randomElement(), suivi de let randomElement = array.remove(at: randomIndex). Vous pouvez même l'intégrer à let randomElement = array.remove(at: array.indices.randomElement()).
Alexander - Réintègre Monica
137

En reprenant ce que Lucas a dit, vous pouvez créer une extension de la classe Array comme ceci:

extension Array {
    func randomItem() -> Element? {
        if isEmpty { return nil }
        let index = Int(arc4random_uniform(UInt32(self.count)))
        return self[index]
    }
}

Par exemple:

let myArray = [1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16]
let myItem = myArray.randomItem() // Note: myItem is an Optional<Int>
Phae Deepsky
la source
2
In swift 2 Ta été renommé en Element.
GDanger
25
Notez qu'un tableau vide provoquera un crash ici
Berik
1
@Berik Eh bien, vous pouvez renvoyer un élément optionnel, puis toujours guardvérifier si le tableau est vide, puis revenir nil.
Harish
1
D'accord. Les tableaux se bloquent en dehors des limites afin d'être performants. Faire appel à arc4randomfait des gains de performance complètement insignifiants. J'ai mis à jour la réponse.
Berik
46

Version Swift 4 :

extension Collection where Index == Int {

    /**
     Picks a random element of the collection.

     - returns: A random element of the collection.
     */
    func randomElement() -> Iterator.Element? {
        return isEmpty ? nil : self[Int(arc4random_uniform(UInt32(endIndex)))]
    }

}
Andrey Gordeev
la source
Cela peut planter avec un index hors limites sur les collections oùstartIndex != 0
dan
21

Dans Swift 2.2, cela peut être généralisé afin que nous ayons:

UInt.random
UInt8.random
UInt16.random
UInt32.random
UInt64.random
UIntMax.random

// closed intervals:

(-3...3).random
(Int.min...Int.max).random

// and collections, which return optionals since they can be empty:

(1..<4).sample
[1,2,3].sample
"abc".characters.sample
["a": 1, "b": 2, "c": 3].sample

Tout d'abord, implémenter la randompropriété statique pour UnsignedIntegerTypes:

import Darwin

func sizeof <T> (_: () -> T) -> Int { // sizeof return type without calling
    return sizeof(T.self)
}

let ARC4Foot: Int = sizeof(arc4random)

extension UnsignedIntegerType {
    static var max: Self { // sadly `max` is not required by the protocol
        return ~0
    }
    static var random: Self {
        let foot = sizeof(Self)
        guard foot > ARC4Foot else {
            return numericCast(arc4random() & numericCast(max))
        }
        var r = UIntMax(arc4random())
        for i in 1..<(foot / ARC4Foot) {
            r |= UIntMax(arc4random()) << UIntMax(8 * ARC4Foot * i)
        }
        return numericCast(r)
    }
}

Alors, pour ClosedIntervals avec UnsignedIntegerTypebornes:

extension ClosedInterval where Bound : UnsignedIntegerType {
    var random: Bound {
        guard start > 0 || end < Bound.max else { return Bound.random }
        return start + (Bound.random % (end - start + 1))
    }
}

Puis (un peu plus compliqué), pour ClosedIntervals avec des SignedIntegerTypelimites (en utilisant les méthodes d'aide décrites plus loin):

extension ClosedInterval where Bound : SignedIntegerType {
    var random: Bound {
        let foot = sizeof(Bound)
        let distance = start.unsignedDistanceTo(end)
        guard foot > 4 else { // optimisation: use UInt32.random if sufficient
            let off: UInt32
            if distance < numericCast(UInt32.max) {
                off = UInt32.random % numericCast(distance + 1)
            } else {
                off = UInt32.random
            }
            return numericCast(start.toIntMax() + numericCast(off))
        }
        guard distance < UIntMax.max else {
            return numericCast(IntMax(bitPattern: UIntMax.random))
        }
        let off = UIntMax.random % (distance + 1)
        let x = (off + start.unsignedDistanceFromMin).plusMinIntMax
        return numericCast(x)
    }
}

... où unsignedDistanceTo, unsignedDistanceFromMinet plusMinIntMaxles méthodes d'assistance peuvent être implémentées comme suit:

extension SignedIntegerType {
    func unsignedDistanceTo(other: Self) -> UIntMax {
        let _self = self.toIntMax()
        let other = other.toIntMax()
        let (start, end) = _self < other ? (_self, other) : (other, _self)
        if start == IntMax.min && end == IntMax.max {
            return UIntMax.max
        }
        if start < 0 && end >= 0 {
            let s = start == IntMax.min ? UIntMax(Int.max) + 1 : UIntMax(-start)
            return s + UIntMax(end)
        }
        return UIntMax(end - start)
    }
    var unsignedDistanceFromMin: UIntMax {
        return IntMax.min.unsignedDistanceTo(self.toIntMax())
    }
}

extension UIntMax {
    var plusMinIntMax: IntMax {
        if self > UIntMax(IntMax.max) { return IntMax(self - UIntMax(IntMax.max) - 1) }
        else { return IntMax.min + IntMax(self) }
    }
}

Enfin, pour toutes les collections où Index.Distance == Int:

extension CollectionType where Index.Distance == Int {
    var sample: Generator.Element? {
        if isEmpty { return nil }
        let end = UInt(count) - 1
        let add = (0...end).random
        let idx = startIndex.advancedBy(Int(add))
        return self[idx]
    }
}

... qui peut être un peu optimisé pour les entiers Ranges:

extension Range where Element : SignedIntegerType {
    var sample: Element? {
        guard startIndex < endIndex else { return nil }
        let i: ClosedInterval = startIndex...endIndex.predecessor()
        return i.random
    }
}

extension Range where Element : UnsignedIntegerType {
    var sample: Element? {
        guard startIndex < endIndex else { return nil }
        let i: ClosedInterval = startIndex...endIndex.predecessor()
        return i.random
    }
}
milos
la source
18

Vous pouvez également utiliser la fonction random () intégrée de Swift pour l'extension:

extension Array {
    func sample() -> Element {
        let randomIndex = Int(rand()) % count
        return self[randomIndex]
    }
}

let array = [1, 2, 3, 4]

array.sample() // 2
array.sample() // 2
array.sample() // 3
array.sample() // 3

array.sample() // 1
array.sample() // 1
array.sample() // 3
array.sample() // 1
NatashaTheRobot
la source
En fait, random () provient du pontage de la bibliothèque Standard C, vous pouvez le voir et ses amis dans Terminal, "man random". Mais content que vous ayez souligné la disponibilité!
David H
1
cela produit la même séquence aléatoire à chaque exécution
iTSangar
1
@iTSangar vous avez raison! rand () est le bon à utiliser. Mise à jour de ma réponse.
NatashaTheRobot
6
Ceci est également sensible au biais modulo.
Aidan Gomez
@mattt a écrit un bel article sur la génération de nombres aléatoires . TL; DR n'importe lequel de la famille arc4random est un meilleur choix.
elitalon
9

Une autre suggestion Swift 3

private extension Array {
    var randomElement: Element {
        let index = Int(arc4random_uniform(UInt32(count)))
        return self[index]
    }
}
prison
la source
4

Les autres répondent mais avec le support Swift 2.

Swift 1.x

extension Array {
    func sample() -> T {
        let index = Int(arc4random_uniform(UInt32(self.count)))
        return self[index]
    }
}

Swift 2.x

extension Array {
    func sample() -> Element {
        let index = Int(arc4random_uniform(UInt32(self.count)))
        return self[index]
    }
}

Par exemple:

let arr = [2, 3, 5, 7, 9, 11, 13, 17, 19, 23, 29, 31]
let randomSample = arr.sample()
Aidan Gomez
la source
2

Une implémentation fonctionnelle alternative avec vérification du tableau vide.

func randomArrayItem<T>(array: [T]) -> T? {
  if array.isEmpty { return nil }
  let randomIndex = Int(arc4random_uniform(UInt32(array.count)))
  return array[randomIndex]
}

randomArrayItem([1,2,3])
Evgenii
la source
2

Voici une extension sur les tableaux avec une vérification de tableau vide pour plus de sécurité:

extension Array {
    func sample() -> Element? {
        if self.isEmpty { return nil }
        let randomInt = Int(arc4random_uniform(UInt32(self.count)))
        return self[randomInt]
    }
}

Vous pouvez l'utiliser aussi simple que ceci :

let digits = Array(0...9)
digits.sample() // => 6

Si vous préférez un Framework qui possède également des fonctionnalités plus pratiques, consultez HandySwift . Vous pouvez l'ajouter à votre projet via Carthage puis l'utiliser exactement comme dans l'exemple ci-dessus:

import HandySwift    

let digits = Array(0...9)
digits.sample() // => 8

De plus, il comprend également une option pour obtenir plusieurs éléments aléatoires à la fois :

digits.sample(size: 3) // => [8, 0, 7]
Jeehut
la source
2

Swift 3

importer GameKit

func getRandomMessage() -> String {

    let messages = ["one", "two", "three"]

    let randomNumber = GKRandomSource.sharedRandom().nextInt(upperBound: messages.count)

    return messages[randomNumber].description

}
Éblouir
la source
2

Swift 3 - simple et facile à utiliser.

  1. Créer un tableau

    var arrayOfColors = [UIColor.red, UIColor.yellow, UIColor.orange, UIColor.green]
    
  2. Créer une couleur aléatoire

    let randomColor = arc4random() % UInt32(arrayOfColors.count)
    
  3. Définissez cette couleur sur votre objet

    your item = arrayOfColors[Int(randomColor)]
    

Voici un exemple d'un SpriteKitprojet mettant à jour un SKLabelNodeavec un aléatoire String:

    let array = ["one","two","three","four","five"]

    let randomNumber = arc4random() % UInt32(array.count)

    let labelNode = SKLabelNode(text: array[Int(randomNumber)])
Timmy Sorensen
la source
2

Si vous souhaitez pouvoir extraire plus d'un élément aléatoire de votre tableau sans doublons , GameplayKit vous a couvert:

import GameplayKit
let array = ["one", "two", "three", "four"]

let shuffled = GKMersenneTwisterRandomSource.sharedRandom().arrayByShufflingObjects(in: array)

let firstRandom = shuffled[0]
let secondRandom = shuffled[1]

Vous avez plusieurs choix pour le caractère aléatoire, voir GKRandomSource :

La GKARC4RandomSourceclasse utilise un algorithme similaire à celui utilisé dans la famille arc4random de fonctions C. (Cependant, les instances de cette classe sont indépendantes des appels aux fonctions arc4random.)

La GKLinearCongruentialRandomSourceclasse utilise un algorithme plus rapide, mais moins aléatoire, que la classe GKARC4RandomSource. (Plus précisément, les bits faibles des nombres générés se répètent plus souvent que les bits hauts.) Utilisez cette source lorsque les performances sont plus importantes que l'imprévisibilité robuste.

La GKMersenneTwisterRandomSourceclasse utilise un algorithme plus lent, mais plus aléatoire, que la classe GKARC4RandomSource. Utilisez cette source lorsqu'il est important que votre utilisation de nombres aléatoires ne présente pas de motifs répétitifs et que les performances soient moins préoccupantes.

bovins
la source
1

Je trouve que l'utilisation de GKRandomSource.sharedRandom () de GameKit fonctionne le mieux pour moi.

import GameKit

let array = ["random1", "random2", "random3"]

func getRandomIndex() -> Int {
    let randomNumber = GKRandomSource.sharedRandom().nextIntWithUpperBound(array.count)
    return randomNumber

ou vous pouvez renvoyer l'objet à l'index aléatoire sélectionné. Assurez-vous que la fonction renvoie d'abord une chaîne, puis renvoyez l'index du tableau.

    return array[randomNumber]

Bref et au point.

djames04
la source
1

Il existe Collectionmaintenant une méthode intégrée :

let foods = ["🍕", "🍔", "🍣", "🍝"]
let myDinner = foods.randomElement()

Si vous souhaitez extraire jusqu'à ndes éléments aléatoires d'une collection, vous pouvez ajouter une extension comme celle-ci:

extension Collection {
    func randomElements(_ count: Int) -> [Element] {
        var shuffledIterator = shuffled().makeIterator()
        return (0..<count).compactMap { _ in shuffledIterator.next() }
    }
}

Et si vous voulez qu'ils soient uniques, vous pouvez utiliser a Set, mais les éléments de la collection doivent être conformes au Hashableprotocole:

extension Collection where Element: Hashable {
    func randomUniqueElements(_ count: Int) -> [Element] {
        var shuffledIterator = Set(shuffled()).makeIterator()
        return (0..<count).compactMap { _ in shuffledIterator.next() }
    }
}
Gigisommo
la source
0

Dernier code swift3, essayez-le fonctionne bien

 let imagesArray = ["image1.png","image2.png","image3.png","image4.png"]

        var randomNum: UInt32 = 0
        randomNum = arc4random_uniform(UInt32(imagesArray.count))
        wheelBackgroundImageView.image = UIImage(named: imagesArray[Int(randomNum)])
Gangireddy Rami Reddy
la source
-2

J'ai trouvé une façon très différente de le faire en utilisant les nouvelles fonctionnalités introduites dans Swift 4.2.

// 👇🏼 - 1 
public func shufflePrintArray(ArrayOfStrings: [String]) -> String {
// - 2 
       let strings = ArrayOfStrings
//- 3
       var stringans =  strings.shuffled()
// - 4
        var countS = Int.random(in: 0..<strings.count)
// - 5
        return stringans[countS] 
}


  1. nous avons déclaré une fonction avec des paramètres prenant un tableau de chaînes et retournant une chaîne.

  2. Ensuite, nous prenons les ArrayOfStrings dans une variable.

  3. Ensuite, nous appelons la fonction mélangée et la stockons dans une variable. (Uniquement pris en charge dans 4.2)
  4. Ensuite, nous déclarons une variable qui enregistre une valeur mélangée du nombre total de la chaîne.
  5. Enfin, nous renvoyons la chaîne mélangée à la valeur d'index de countS.

Il s'agit essentiellement de mélanger le tableau de chaînes, puis de choisir au hasard le nombre du nombre total de comptages, puis de renvoyer l'index aléatoire du tableau mélangé.

Nachos et Cheetos
la source