2015-02-12 12 views
9

Od Swift 1.2 (dostępny jako wersja beta) Apple wprowadza typ kolekcji Set.Jak uzyskać losowy element z zestawu w Swift?

Say, Mam zestaw jak:

var set = Set<Int>(arrayLiteral: 1, 2, 3, 4, 5) 

Teraz chcę dostać losowy element z niego. Pytanie brzmi: jak? Set nie udostępnia subscript(Int), jak Array. Zamiast tego ma subscript(SetIndex<T>). Ale po pierwsze, SetIndex<T> nie ma dostępnych inicjalizatorów (stąd nie mogę po prostu utworzyć indeksu z potrzebnym offsetem), a po drugie, nawet jeśli mogę uzyskać indeks dla pierwszego elementu w zbiorze (var startIndex = set.startIndex), to jedyny sposób może dostać się do N-tego indeksu przez kolejne połączenia do successor().

Zatem można zobaczyć tylko dwie możliwości w tym momencie, zarówno brzydkie i kosztownych:

  • Konwersja zestawu do tablicy (var array = [Int](set)) i dokonuje indeksu dolnego (który idealnie przyjmuje Int); lub
  • Pobierz indeks pierwszego elementu w zbiorze, przechodź przez łańcuchy metod successor(), aby uzyskać indeks N-ty, a następnie odczytaj odpowiedni element za pomocą indeksu zestawu.

Czy tęsknię za jakimś innym sposobem?

UPDATE

Jak @rintaro wskazał, należy użyć wcześniej() funkcja natychmiast dostać się do indeksu chcę. Np .:

var set = Set<Int>(arrayLiteral: 1, 2, 3, 4, 5) 
let randomOffset = Int(arc4random_uniform(UInt32(set.count))) 
let random = set[advance(set.startIndex, randomOffset)] 

UPDATE 2

Okazuje się, że advance() ma złożoność O (n), co zasadniczo czyni ją równoważne prostu przechodząc przez łańcuch successor().

+0

Dzięki @rintaro! Właśnie tego szukałem. – courteouselk

Odpowiedz

8

Prawdopodobnie najlepszym rozwiązaniem jest advance który idzie successor dla Ciebie:

func randomElementIndex<T>(s: Set<T>) -> T { 
    let n = Int(arc4random_uniform(UInt32(s.count))) 
    let i = advance(s.startIndex, n) 
    return s[i] 
} 

(EDIT: Heh; Zauważyliśmy, że faktycznie aktualizowany kwestię włączenia tę odpowiedź zanim dodałem go do mojej odpowiedzi ... dobrze, nadal jest to dobry pomysł i nauczyłem się czegoś.: D)

Możesz także chodzić po zestawie, a nie w indeksach (to była moja pierwsza myśl, ale potem przypomniałem sobie advance).

func randomElement<T>(s: Set<T>) -> T { 
    let n = Int(arc4random_uniform(UInt32(s.count))) 
    for (i, e) in enumerate(s) { 
     if i == n { return e } 
    } 
    fatalError("The above loop must succeed") 
} 
+0

Tak. Jednak zabawną częścią jest to, że 'advance()' w przypadku Seta ma złożoność O (N). Technicznie jest to tak samo jak przechodzenie przez łańcuch 'następcy()', po prostu wygląda o wiele bardziej zwięźle i jasno. Zadałem podobne pytanie na forum programistów Apple i zwróciło to uwagę pracowników => może w przyszłym wydaniu Swifta pojawią się lepsze narzędzia do robienia tego, co chcę. Na razie prawdopodobnie pozostanę przy 'advance()'. Zestawy, które będę używał, nie są tak duże. – courteouselk

+0

Metoda zaawansowana już się nie kompiluje, tak jak w przypadku Xcode 7 Beta 6 –

+2

Justin Lewis wszystko co się zmieniło to to, że musisz teraz wywołać 'advancedBy (_ :)' na indeksie 'extension Set {func randomElement() -> Element {let n = Int (arc4random_uniform (UInt32 (count))); let i = startIndex.advancedBy (n); return self [i];}} ' – griotspeak

1

Jeśli chcesz „random” elementu z Set następnie użyć:

/// A member of the set, or `nil` if the set is empty. 
var first: T? { get } 

Pobierz 0TH indeksu lub indeks 1.000.000 ma znaczenia - wszystkie są arbitralne przedmiot.

Ale, jeśli chcesz powtarzające się połączenia, aby zwrócić za każdym razem inny, prawdopodobnie element, wtedy first może nie pasować do rachunku.

+7

Jest to mniej więcej ten sam algorytm co https://www.xkcd.com/221/ –

+4

I mniej więcej tak samo, jak "Znajdź x" http://bit.ly/173dqTe – GoZoner

+0

Oczywiście, jak mógłbyś to powiedzieć. pierwszy. różni się od "walk over N-1, return the Nth"? Czy Swift daje gwarancję, że kolejność ruchu jest stała w czasie? Niektóre języki tego nie robią (ponieważ wartość skrótu Set zależy od adresu pamięci oraz od systemów pamięciowych zmieniają się adresy pamięci). – GoZoner

5
extension Set { 
    func randomElement() -> Element? { 
     return count == 0 ? nil : self[advance(self.startIndex, Int(arc4random()) % count)] 
    } 
} 
2

Według powyższych uwag re Swift aktualizacji, używany niewielką zmianę o przedłużenie do ustaw:

func randomElement() -> Element? 
{ 
    let randomInt = Int(arc4random_uniform(UInt32(self.count))) 
    let index = startIndex.advancedBy(randomInt) 
    return count == 0 ? nil: self[index] 
} 
4

w Swift 3

extension Set { 
    public func randomObject() -> Element? { 
     let n = Int(arc4random_uniform(UInt32(self.count))) 
     let index = self.index(self.startIndex, offsetBy: n) 
     return self.count > 0 ? self[index] : nil 
    } 
}