Wählen Sie ein zufälliges Element aus einem Array

189

Angenommen, ich habe ein Array und möchte ein Element zufällig auswählen.

Was wäre der einfachste Weg, dies zu tun?

Der offensichtliche Weg wäre array[random index]. Aber vielleicht gibt es so etwas wie Rubins array.sample? Oder wenn nicht, könnte eine solche Methode mithilfe einer Erweiterung erstellt werden?

Fela Winkelmolen
quelle
1
Haben Sie schon andere Methoden ausprobiert?
Ford Präfekt
Ich würde es versuchen array[random number from 0 to length-1], aber ich kann nicht herausfinden, wie man schnell ein zufälliges int generiert. Ich würde es beim Stapelüberlauf fragen, wenn ich nicht blockiert wäre :) Ich wollte die Frage nicht mit halben Lösungen verschmutzen, wenn es vielleicht welche gibt so etwas wie Rubysarray.sample
Fela Winkelmolen
1
Sie verwenden arc4random () wie in Obj-C
Arbitur
Keine Erklärung, warum Ihre Frage nicht das gleiche Feedback wie das von JQuery erhalten hat. Im Allgemeinen sollten Sie diese Richtlinien befolgen, wenn Sie eine Frage stellen. Wie kann man eine gute Frage stellen? . Lassen Sie es so aussehen, als würden Sie sich ein wenig Mühe geben, um eine Lösung zu finden, bevor Sie jemanden um Hilfe bitten. Wenn ich google "Zufallszahl schnell auswählen", wird die erste Seite mit Antworten gefüllt, die arc4random_uniform vorschlagen. Auch RTFD ... "Lesen Sie die Dokumentation". Es ist überraschend, wie viele Fragen auf diese Weise beantwortet werden können.
Austin A
Vielen Dank für Ihr freundliches Feedback. Ja, ich denke, ich hätte die Frage selbst beantworten sollen, aber es schien einfach genug, dass es schön war, jemand anderem die fast freien Reputationspunkte zu geben. Und ich habe es geschrieben, als nicht einmal die offiziellen Apple Swift-Dokumente veröffentlicht wurden. Zu diesem Zeitpunkt gab es definitiv keine Google-Ergebnisse. Aber die Frage war einmal bei -12, also bin ich ziemlich zuversichtlich, dass es irgendwann positiv sein wird :)
Fela Winkelmolen

Antworten:

320

Swift 4.2 und höher

Der neue empfohlene Ansatz ist eine im Erfassungsprotokoll integrierte Methode : randomElement(). Es wird eine Option zurückgegeben, um den leeren Fall zu vermeiden, gegen den ich zuvor angenommen habe.

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

Wenn Sie das Array nicht erstellen und nicht garantiert sind, dass die Anzahl> 0 ist, sollten Sie Folgendes tun:

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

Swift 4.1 und niedriger

Um Ihre Frage zu beantworten, können Sie dies tun, um eine zufällige Array-Auswahl zu erreichen:

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

Die Castings sind hässlich, aber ich glaube, sie sind erforderlich, es sei denn, jemand anderes hat einen anderen Weg.

Lucas Derraugh
quelle
4
Warum bietet Swift keinen Zufallszahlengenerator an, der ein Int zurückgibt? Diese 2. Zeile scheint sehr ausführlich zu sein, nur um zufällig ausgewählte Int zurückzugeben. Gibt es einen rechnerischen / syntaktischen Vorteil bei der Rückgabe eines UInt32 im Gegensatz zu einem Int? Warum bietet Swift keine Int-Alternative zu dieser Funktion an oder lässt einen Benutzer nicht angeben, welche Art von Ganzzahl er zurückgeben möchte?
Austin A
Um eine Notiz hinzuzufügen, könnte diese Zufallszahlengeneratormethode "Modulo Bias" verhindern. Siehe man arc4randomund stackoverflow.com/questions/10984974/…
Kent Liau
1
@AustinA, Swift 4.2 verfügt über eine native Zufallszahlengeneratorfunktion, die für alle erwarteten Skalardatentypen implementiert ist: Int, Double, Float, UInt32 usw. Außerdem können Sie Zielbereiche für die Werte bereitstellen. Sehr praktisch. Sie können Array [Int.random (0 .. <array.count)] `in Swift 4.2
Duncan C
Ich wünschte, Swift 4.2 hätte zusätzlich eine removeRandomElement()Funktion implementiert randomElement(). Es würde modelliert werden removeFirst(), aber ein Objekt an einem zufälligen Index entfernen.
Duncan C
@DuncanC Sie sollten dies vermeiden 0..<array.count(aus mehreren Gründen, vor allem, weil es für Slices nicht funktioniert und fehleranfällig ist). Sie können tun let randomIndex = array.indices.randomElement(), gefolgt von let randomElement = array.remove(at: randomIndex). Sie könnten es sogar inline let randomElement = array.remove(at: array.indices.randomElement()).
Alexander - Reinstate Monica
137

Nach dem, was Lucas gesagt hat, können Sie eine Erweiterung der Array-Klasse wie folgt erstellen:

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

Beispielsweise:

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
quelle
2
In Swift 2 Twurde in umbenannt Element.
GDanger
25
Beachten Sie, dass ein leeres Array hier einen Absturz verursachen wird
Berik
1
@Berik Nun, Sie könnten ein optionales Element zurückgeben und dann immer guardüberprüfen, ob das Array leer ist, und dann zurückkehren nil.
Harish
1
Einverstanden. Arrays stürzen außerhalb des Bereichs ab, sodass sie performant sein können. Das Aufrufen von arc4randommacht Leistungssteigerungen völlig unbedeutend. Ich habe die Antwort aktualisiert.
Berik
45

Swift 4 Version:

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
quelle
Dies kann mit einem Index außerhalb der Grenzen auf Sammlungen abstürzen, wostartIndex != 0
dan
21

In Swift 2.2 kann dies so verallgemeinert werden, dass wir:

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

Implementieren der statischen randomEigenschaft für 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)
    }
}

Dann für ClosedIntervals mit UnsignedIntegerTypeGrenzen:

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

Dann (etwas komplizierter) für ClosedIntervals mit SignedIntegerTypeGrenzen (unter Verwendung der weiter unten beschriebenen Hilfsmethoden):

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

... wo unsignedDistanceTo, unsignedDistanceFromMinund plusMinIntMaxHilfsmethoden können wie folgt umgesetzt werden:

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

Schließlich für alle Sammlungen, in denen 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]
    }
}

... die ein wenig für ganze RangeZahlen optimiert werden kann :

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
quelle
18

Sie können auch die integrierte Funktion random () von Swift für die Erweiterung verwenden:

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
quelle
Tatsächlich ist random () aus der Standard C-Bibliotheksüberbrückung, Sie können es und Freunde im Terminal "man random" sehen. Aber froh, dass Sie auf die Verfügbarkeit hingewiesen haben!
David H
1
Dies erzeugt jedes Mal die gleiche zufällige Sequenz
iTSangar
1
@iTSangar du hast recht! rand () ist das richtige. Aktualisiere meine Antwort.
NatashaTheRobot
6
Dies ist auch anfällig für Modulo-Vorspannung.
Aidan Gomez
@mattt hat einen schönen Artikel über das Generieren von Zufallszahlen geschrieben . TL; DR aus der arc4random-Familie ist die bessere Wahl.
Elitalon
9

Ein weiterer Vorschlag von Swift 3

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

Folgende andere antworten aber mit Swift 2 Unterstützung.

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

Z.B:

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

Eine alternative funktionale Implementierung mit Überprüfung auf leeres Array.

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
quelle
2

Hier ist eine Erweiterung für Arrays mit einer leeren Array-Prüfung für mehr Sicherheit:

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

Sie können es so einfach verwenden :

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

Wenn Sie ein Framework bevorzugen , das auch einige weitere praktische Funktionen bietet, lesen Sie HandySwift . Sie können es über Karthago zu Ihrem Projekt hinzufügen und dann genau wie im obigen Beispiel verwenden:

import HandySwift    

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

Zusätzlich enthält es eine Option, um mehrere zufällige Elemente gleichzeitig abzurufen :

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

Swift 3

GameKit importieren

func getRandomMessage() -> String {

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

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

    return messages[randomNumber].description

}
Blenden
quelle
2

Swift 3 - einfach einfach zu bedienen.

  1. Array erstellen

    var arrayOfColors = [UIColor.red, UIColor.yellow, UIColor.orange, UIColor.green]
  2. Erstellen Sie eine zufällige Farbe

    let randomColor = arc4random() % UInt32(arrayOfColors.count)
  3. Stellen Sie diese Farbe auf Ihr Objekt ein

    your item = arrayOfColors[Int(randomColor)]

Hier ist ein Beispiel aus einem SpriteKitProjekt, das a SKLabelNodemit einem Zufall aktualisiert String:

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

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

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

Wenn Sie in der Lage sein möchten, mehr als ein zufälliges Element ohne Duplikate aus Ihrem Array herauszuholen, bietet GameplayKit Folgendes:

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

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

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

Sie haben einige Möglichkeiten für die Zufälligkeit, siehe GKRandomSource :

Das GKARC4RandomSource Klasse verwendet einen Algorithmus ähnlich dem in der arc4random-Familie von C-Funktionen verwendeten. (Instanzen dieser Klasse sind jedoch unabhängig von Aufrufen der arc4random-Funktionen.)

Die GKLinearCongruentialRandomSourceKlasse verwendet einen Algorithmus, der schneller, aber weniger zufällig ist als die GKARC4RandomSource-Klasse. (Insbesondere wiederholen sich die niedrigen Bits der generierten Zahlen häufiger als die hohen Bits.) Verwenden Sie diese Quelle, wenn die Leistung wichtiger ist als die robuste Unvorhersehbarkeit.

Die GKMersenneTwisterRandomSourceKlasse verwendet einen Algorithmus, der langsamer, aber zufälliger ist als die GKARC4RandomSource-Klasse. Verwenden Sie diese Quelle, wenn es wichtig ist, dass Ihre Verwendung von Zufallszahlen keine sich wiederholenden Muster zeigt und die Leistung weniger wichtig ist.

Vieh
quelle
1

Ich finde, dass die Verwendung von GKRandomSource.sharedRandom () von GameKit am besten für mich funktioniert.

import GameKit

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

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

oder Sie können das Objekt an dem ausgewählten Zufallsindex zurückgeben. Stellen Sie sicher, dass die Funktion zuerst einen String zurückgibt und dann den Index des Arrays zurückgibt.

    return array[randomNumber]

Kurz und auf den Punkt.

djames04
quelle
1

Derzeit gibt es eine integrierte Methode Collection:

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

Wenn Sie bis zu nzufällige Elemente aus einer Sammlung extrahieren möchten, können Sie eine Erweiterung wie die folgende hinzufügen:

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

Und wenn Sie möchten, dass sie eindeutig sind, können Sie a verwenden Set, aber die Elemente der Sammlung müssen dem HashableProtokoll entsprechen:

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

Der neueste swift3-Code funktioniert einwandfrei

 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
quelle
-2

Mit den in Swift 4.2 eingeführten neuen Funktionen habe ich einen ganz anderen Weg gefunden.

// 👇🏼 - 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. Wir haben eine Funktion mit Parametern deklariert, die ein Array von Strings nehmen und einen String zurückgeben.

  2. Dann nehmen wir die ArrayOfStrings in eine Variable.

  3. Dann rufen wir die Shuffled-Funktion auf und speichern sie in einer Variablen. (Wird nur in 4.2 unterstützt)
  4. Dann deklarieren wir eine Variable, die einen gemischten Wert der Gesamtanzahl des Strings speichert.
  5. Zuletzt geben wir die gemischte Zeichenfolge mit dem Indexwert von countS zurück.

Es mischt im Grunde das Array von Strings und hat dann auch eine zufällige Auswahl der Anzahl der Gesamtzahl der Zählungen und gibt dann den zufälligen Index des gemischten Arrays zurück.

Nachos und Cheetos
quelle