2016-06-15 1 views
25

Wie kann ich die folgende Funktion in swift 3 konvertieren? Derzeit wird ein Binary operator '..<' cannot be applied to operands of type 'Int' and 'Self.IndexDistance' Fehler angezeigt.Shuffle array swift 3

extension MutableCollection where Index == Int { 
    /// Shuffle the elements of `self` in-place. 
    mutating func shuffleInPlace() { 
    // empty and single-element collections don't shuffle 
    if count < 2 { return } 

    for i in 0..<count - 1 { //error takes place here 
     let j = Int(arc4random_uniform(UInt32(count - i))) + i 
     guard i != j else { continue } 
     swap(&self[i], &self[j]) 
    } 
    } 
} 

Referenz: https://stackoverflow.com/a/24029847/5222077

+0

https: // Stackoverflow.com/a/27261991/2303865 –

+0

Mögliches Duplikat von [Wie mische ich ein Array in Swift?] (https://stackoverflow.com/questions/24026510/how-do-i-shuffle-an-array-in-swift) –

+0

Die Frage ist obsolet, da die Referenz für Swift 3 aktualisiert wurde. –

Antwort

75

count gibt eine IndexDistance, die die Art der Beschreibung ist der Abstand zwischen zwei Sammlungsindizes. IndexDistance ist erforderlich, um ein SignedInteger zu sein, muss aber kein Int sein und kann von Index abweichen. Daher ist es nicht möglich den Bereich 0..<count - 1 zu erstellen.

Eine Lösung ist startIndex und endIndex verwenden anstelle von 0 und count:

extension MutableCollection where Index == Int { 
    /// Shuffle the elements of `self` in-place. 
    mutating func shuffle() { 
     // empty and single-element collections don't shuffle 
     if count < 2 { return } 

     for i in startIndex ..< endIndex - 1 { 
      let j = Int(arc4random_uniform(UInt32(endIndex - i))) + i 
      if i != j { 
       swap(&self[i], &self[j]) 
      } 
     } 
    } 
} 

Ein weiterer Vorteil ist, dass dies auch korrekt mit Array Scheiben arbeitet (wobei der Index des ersten Elements nicht notwendigerweise Null).

anzumerken, dass nach dem neuen "Swift API Design Guidelines", shuffle() ist die "richtige" Name für einen mutierenden Shuffle-Methode, und shuffled() für die nicht-Mutieren Gegenstück, die ein Array zurückgibt:

extension Collection { 
    /// Return a copy of `self` with its elements shuffled 
    func shuffled() -> [Iterator.Element] { 
     var list = Array(self) 
     list.shuffle() 
     return list 
    } 
} 

Update: Eine (noch allgemeinere) Swift 3-Version wurde in der Zwischenzeit zu How do I shuffle an array in Swift? hinzugefügt.


Für Swift 4 (Xcode 9) man hat den Anruf an die swap() Funktion durch einen Aufruf der Methode swapAt() der Sammlung zu ersetzen. Auch die Beschränkung des Index Typ nicht mehr benötigt wird:

extension MutableCollection { 
    /// Shuffle the elements of `self` in-place. 
    mutating func shuffle() { 
     // empty and single-element collections don't shuffle 
     if count < 2 { return } 

     for i in indices.dropLast() { 
      let diff = distance(from: i, to: endIndex) 
      let j = index(i, offsetBy: numericCast(arc4random_uniform(numericCast(diff)))) 
      swapAt(i, j) 
     } 
    } 
} 

SE-0173 Add MutableCollection.swapAt(_:_:) über swapAt für weitere Informationen.

+3

Sie sind ein Genie –

+0

Ich fühlte mich einfach so, als sollte ich darauf hinweisen, dass das '+ i' in' let j' wahrscheinlich '+ startIndex' sein sollte. Sonst führt dies sehr wahrscheinlich dazu, dass der Index außerhalb der Grenzen liegt. –

+0

@ BjarkeH.Søndergaard: Ich bin ziemlich sicher, dass der obige Code richtig ist, und ich hatte es mit Arrays und Array-Slices getestet. 'i' liegt im Bereich' startIndex .. = i> = startIndex' und 'j

6

Ich würde vorschlagen, einfach Arrays anstatt zu versuchen, zu erweitern, dies zu Sammlungen im Allgemeinen schlurfenden:

extension Array { 
    mutating func shuffle() { 
     for i in (0..<self.count).reversed() { 
      let ix1 = i 
      let ix2 = Int(arc4random_uniform(UInt32(i+1))) 
      (self[ix1], self[ix2]) = (self[ix2], self[ix1]) 
     } 
    } 
} 
9

Es ist ein Fisher-Yates-Verfahren in GameKit:

import GameKit 
let unshuffledArray = [1,2,3,4] 
let shuffledArray = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: unshuffledArray) 
print(shuffledArray) 

Sie können auch in geben und speichern einen zufälligen Samen, so dass Sie die gleiche Sequenz von Pseudo-Zufalls-Shuffle-Werte erhalten jedes Mal, liefern Sie das gleiche Saatgut in Fall, dass Sie eine Simulation neu erstellen müssen.

import GameKit 
let unshuffledArray = [1,2,3,4] 
let randomSource = GKLinearCongruentialRandomSource(seed: 1) 
let shuffledArray = randomSource.arrayByShufflingObjects(in: unshuffledArray) 
//Always [1,4,2,3] 
print(shuffledArray) 
+0

Auch hier beobachtet: http://stackoverflow.com/a/30858350/1187415 :) –

0

Sie können die NSArray Erweiterung von GameplayKit Rahmen für diesen Einsatz:

import GameplayKit 

extension Collection { 
    func shuffled() -> [Iterator.Element] { 
     let shuffledArray = (self as? NSArray)?.shuffled() 
     let outputArray = shuffledArray as? [Iterator.Element] 
     return outputArray ?? [] 
    } 
    mutating func shuffle() { 
     if let selfShuffled = self.shuffled() as? Self { 
      self = selfShuffled 
     } 
    } 
} 

// Usage example: 

var numbers = [1,2,3,4,5] 
numbers.shuffle() 

print(numbers) // output example: [2, 3, 5, 4, 1] 

print([10, "hi", 9.0].shuffled()) // output example: [hi, 10, 9]