Wybierz losowy element z tablicy

189

Załóżmy, że mam tablicę i chcę wybrać jeden element losowo.

Jaki byłby najprostszy sposób to zrobić?

Oczywistym sposobem byłoby array[random index]. Ale może jest coś takiego jak rubin array.sample? A jeśli nie, można stworzyć taką metodę za pomocą rozszerzenia?

Fela Winkelmolen
źródło
1
Czy wypróbowałeś już inne metody?
ford prefekt
Spróbowałbym array[random number from 0 to length-1], ale nie mogę znaleźć sposobu szybkiego wygenerowania losowej liczby całkowitej, gdybym nie został zablokowany, zapytałbym ją o przepełnienie stosu :) Nie chciałem zanieczyszczać pytania pół rozwiązaniami, kiedy może coś jak rubyarray.sample
Fela Winkelmolen
1
Używasz arc4random () jak w Obj-C
Arbitur
Brak wyjaśnienia, dlaczego twoje pytanie nie otrzymało takiej samej informacji zwrotnej jak odpowiednik JQuery. Ale ogólnie powinieneś kierować się tymi wskazówkami, zamieszczając pytanie. Jak zadać dobre pytanie? . Spraw, by wyglądało na to, że włożyłeś trochę wysiłku w znalezienie rozwiązania, zanim poprosisz kogoś o pomoc. Kiedy wyszukuję w Google „wybieram losową liczbę swift”, pierwsza strona jest wypełniona odpowiedziami sugerującymi arc4random_uniform. Ponadto RTFD ... „przeczytaj dokumentację techniczną”. Zaskakujące jest, na ile pytań można odpowiedzieć w ten sposób.
Austin A
Dziękujemy za miłą opinię. Tak, chyba powinienem sam odpowiedzieć na to pytanie, ale wydawało się dość łatwe, że miło było dać komuś innemu prawie bezpłatne punkty reputacyjne. I napisałem to, gdy nawet oficjalne dokumenty Apple nie były publiczne, zdecydowanie nie było wtedy wyników Google. Ale pytanie było raz na -12, więc jestem całkiem pewny, że ostatecznie będzie pozytywne :)
Fela Winkelmolen

Odpowiedzi:

321

Swift 4.2 i wyżej

Nowy Zalecane podejście jest wbudowany w metodzie na protokole kolekcji: randomElement(). Zwraca opcjonalne, aby uniknąć pustej skrzynki, którą wcześniej zakładałem.

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

Jeśli nie utworzysz tablicy i nie masz gwarantowanej liczby> 0, powinieneś zrobić coś takiego:

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

Swift 4.1 i poniżej

Aby odpowiedzieć na twoje pytanie, możesz to zrobić, aby uzyskać losowy wybór tablicy:

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

Castingi są brzydkie, ale uważam, że są wymagane, chyba że ktoś inny ma inny sposób.

Lucas Derraugh
źródło
4
Dlaczego Swift nie oferuje generatora liczb losowych, który zwraca Int? Ta druga linia wydaje się bardzo szczegółowa, aby powrócić losowo wybrana Int. Czy jest jakaś przewaga obliczeniowa / składniowa zwracania UInt32 w przeciwieństwie do Int? Ponadto, dlaczego Swift nie oferuje alternatywy Int dla tej funkcji ani nie pozwala użytkownikowi określić, jaki typ liczby całkowitej ma być zwracany?
Austin A
Aby dodać notatkę, ta metoda generowania liczb losowych może zapobiec „tendencyjności modulo”. Odwołaj się man arc4randomi stackoverflow.com/questions/10984974/...
Kent Liau
1
@AustinA, Swift 4.2 DOES posiada natywną funkcję generatora liczb losowych, która jest zaimplementowana we wszystkich skalarnych typach danych, których możesz oczekiwać: Int, Double, Float, UInt32 itp. I pozwala ci podać zakresy wartości docelowych. Bardzo przydatny. Możesz użyć tablicy [Losowo (0 .. <liczba tablic)] `w Swift 4.2
Duncan C
Chciałbym, aby Swift 4.2 wdrożył dodatkowo removeRandomElement()funkcję randomElement(). Zostanie modelowany removeFirst(), ale usunie obiekt o losowym indeksie.
Duncan C
@ DuncanC Powinieneś unikać 0..<array.count(z wielu powodów, z których najważniejszym jest to, że nie działa na plastry i jest podatny na błędy). Możesz zrobić let randomIndex = array.indices.randomElement(), a następnie let randomElement = array.remove(at: randomIndex). Możesz nawet wstawić to do let randomElement = array.remove(at: array.indices.randomElement()).
Alexander - Przywróć Monikę
137

Opierając się na tym, co powiedział Lucas, możesz utworzyć rozszerzenie klasy Array w następujący sposób:

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

Na przykład:

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
źródło
2
W swift 2 Tzmieniono nazwę na Element.
GDanger
25
Zauważ, że pusta tablica spowoduje tutaj awarię
Berik
1
@Berik Cóż, możesz zwrócić opcjonalny element, a następnie zawsze guardsprawdzić, czy tablica jest pusta, a następnie zwrócić nil.
Harish
1
Zgoda. Tablice ulegają awarii poza linią, aby mogły być wydajne. Wezwanie do arc4randompowoduje, że wszelkie zyski stają się zupełnie nieistotne. Zaktualizowałem odpowiedź.
Berik
45

Wersja 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
źródło
To może upaść z poza granic indeksu na zbiorach gdziestartIndex != 0
Dan
21

W Swift 2.2 można to uogólnić, dzięki czemu mamy:

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

Po pierwsze, implementacja randomwłaściwości statycznej dla 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)
    }
}

Następnie dla ClosedIntervals z UnsignedIntegerTypegranicami:

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

Następnie (nieco bardziej zaangażowany), dla ClosedIntervals z SignedIntegerTypegranicami (przy użyciu metod pomocniczych opisanych poniżej):

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

... gdzie unsignedDistanceTo, unsignedDistanceFromMini plusMinIntMaxpomocnika metody mogą być realizowane w następujący sposób:

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

Wreszcie dla wszystkich kolekcji, w których 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]
    }
}

... które można trochę zoptymalizować dla liczb całkowitych Range:

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
źródło
18

Do rozszerzenia możesz także użyć wbudowanej funkcji losowej () Swift:

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
źródło
W rzeczywistości random () pochodzi ze standardowego mostka biblioteki C, można go zobaczyć i znajomych w terminalu „man random”. Ale cieszę się, że zwróciłeś uwagę na dostępność!
David H
1
daje to tę samą losową sekwencję za każdym razem
iTSangar 10.04.15
1
@iTSangar masz rację! rand () jest poprawny do użycia. Aktualizacja mojej odpowiedzi.
NatashaTheRobot
6
Jest to również podatne na błąd modulo.
Aidan Gomez
@mattt napisał fajny artykuł na temat generowania liczb losowych . TL; DR dowolna rodzina arc4random jest lepszym wyborem.
elitalon
9

Kolejna sugestia dotycząca Swift 3

private extension Array {
    var randomElement: Element {
        let index = Int(arc4random_uniform(UInt32(count)))
        return self[index]
    }
}
więzienie
źródło
4

Odpowiadają inni, ale z obsługą 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]
    }
}

Na przykład:

let arr = [2, 3, 5, 7, 9, 11, 13, 17, 19, 23, 29, 31]
let randomSample = arr.sample()
Aidan Gomez
źródło
2

Alternatywna implementacja funkcjonalna z opcją sprawdzania pustej tablicy.

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
źródło
2

Oto rozszerzenie tablic z opcją sprawdzania pustej tablicy dla większego bezpieczeństwa:

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

Możesz użyć tego tak prostego :

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

Jeśli wolisz Framework, który ma również kilka przydatnych funkcji, sprawdź HandySwift . Możesz dodać go do swojego projektu za pośrednictwem Kartaginy, a następnie użyć go dokładnie tak, jak w powyższym przykładzie:

import HandySwift    

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

Dodatkowo zawiera również opcję uzyskania wielu losowych elementów jednocześnie :

digits.sample(size: 3) // => [8, 0, 7]
Jeehut
źródło
2

Szybki 3

importuj GameKit

func getRandomMessage() -> String {

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

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

    return messages[randomNumber].description

}
Oślepiać
źródło
2

Swift 3 - prosty, łatwy w użyciu.

  1. Utwórz tablicę

    var arrayOfColors = [UIColor.red, UIColor.yellow, UIColor.orange, UIColor.green]
  2. Utwórz losowy kolor

    let randomColor = arc4random() % UInt32(arrayOfColors.count)
  3. Ustaw ten kolor na swoim obiekcie

    your item = arrayOfColors[Int(randomColor)]

Oto przykład z SpriteKitprojektu aktualizującego SKLabelNodelosowo String:

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

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

    let labelNode = SKLabelNode(text: array[Int(randomNumber)])
Timmy Sorensen
źródło
2

Jeśli chcesz mieć więcej niż jeden losowy element z tablicy bez duplikatów , GameplayKit zapewnia:

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

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

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

Masz kilka możliwości losowości, zobacz GKRandomSource :

The GKARC4RandomSourceKlasy wykorzystuje algorytm podobny do stosowanego w arc4random rodzinę funkcji C. (Jednak instancje tej klasy są niezależne od wywołań funkcji arc4random.)

GKLinearCongruentialRandomSourceKlasa wykorzystuje algorytm, który jest szybszy, ale mniej przypadkowy, niż klasa GKARC4RandomSource. (W szczególności niskie bity generowanych liczb powtarzają się częściej niż wysokie bity.) Użyj tego źródła, gdy wydajność jest ważniejsza niż solidna nieprzewidywalność.

GKMersenneTwisterRandomSourceKlasa wykorzystuje algorytm, który jest wolniejszy, ale bardziej losowy, niż klasa GKARC4RandomSource. Użyj tego źródła, gdy ważne jest, aby korzystanie z liczb losowych nie zawierało powtarzających się wzorców, a wydajność jest mniej istotna.

bydło
źródło
1

Uważam, że użycie GKRandomSource.sharedRandom () GameKit działa dla mnie najlepiej.

import GameKit

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

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

lub możesz zwrócić obiekt o wybranym losowym indeksie. Upewnij się, że funkcja najpierw zwraca łańcuch, a następnie indeks tablicy.

    return array[randomNumber]

Krótko i na temat.

djames04
źródło
1

CollectionTeraz jest wbudowana metoda :

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

Jeśli chcesz wyodrębnić nlosowe elementy z kolekcji, możesz dodać takie rozszerzenie:

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

A jeśli chcesz, aby były unikalne, możesz użyć a Set, ale elementy kolekcji muszą być zgodne z Hashableprotokołem:

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

Najnowszy kod swift3 wypróbuj go dobrze

 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
źródło
-2

Odkryłem zupełnie inny sposób, korzystając z nowych funkcji wprowadzonych w 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. zadeklarowaliśmy funkcję z parametrami pobierającymi tablicę ciągów i zwracającą ciąg.

  2. Następnie bierzemy ArrayOfStrings w zmiennej.

  3. Następnie wywołujemy funkcję odtwarzania losowego i przechowujemy ją w zmiennej. (Obsługiwane tylko w wersji 4.2)
  4. Następnie deklarujemy zmienną, która zapisuje przetasowaną wartość całkowitej liczby Ciągów.
  5. Na koniec zwracamy tasowany ciąg według wartości indeksu countS.

Zasadniczo tasuje tablicę ciągów, a następnie losowo wybiera liczbę z całkowitej liczby zliczeń, a następnie zwraca losowy indeks przetasowanej tablicy.

Nachos i Cheetos
źródło