2016-06-15 54 views
25

Come posso convertire la funzione seguente a swift 3? Attualmente si verifica un errore Binary operator '..<' cannot be applied to operands of type 'Int' and 'Self.IndexDistance'.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]) 
    } 
    } 
} 

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

+0

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

+0

Possibile duplicato di [Come mischiare un array in Swift?] (https://stackoverflow.com/questions/24026510/how-do-i-shuffle-an-array-in-swift) –

+0

La domanda è obsoleta, poiché il riferimento è stato aggiornato per Swift 3. –

risposta

75

count restituisce un IndexDistance che è il tipo descrive distanza tra due indici di raccolta. IndexDistance è richiesto per essere un SignedInteger, ma non deve essere un Int e può essere diverso da Index. Pertanto non è possibile creare nell'intervallo 0..<count - 1.

Una soluzione è quella di utilizzare startIndex e endIndex anziché 0 e 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]) 
      } 
     } 
    } 
} 

Un altro vantaggio è che questo funziona correttamente anche con matrice fette (dove l'indice del primo elemento non è necessariamente zero).

nota che in base al nuovo "Swift API Design Guidelines", shuffle() è il nome "corretto" per un metodo mutante shuffle, e shuffled() per il non-mutanti controparte, che restituisce un array:

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

Aggiornamento: A (ancora più generale) la versione Swift 3 è stata aggiunta a How do I shuffle an array in Swift? nel frattempo.


Per Swift 4 (Xcode 9) si deve sostituire la chiamata alla funzione swap() da una chiamata al metodo della raccolta swapAt(). Inoltre non è più necessario la restrizione del tipo Index:

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

Vedi SE-0173 Add MutableCollection.swapAt(_:_:) per ulteriori informazioni su swapAt.

+3

Sei un genio –

+0

Ho solo pensato che dovrei indicare che '+ i' in' let j' probabilmente dovrebbe essere '+ startIndex'. Altrimenti è molto probabile che ciò porti a un indice fuori limite. –

+0

@ BjarkeH.Søndergaard: Sono abbastanza sicuro che il codice sopra riportato sia corretto e l'ho testato sia con array sia con sezioni di array. 'i' è nell'intervallo' startIndex .. = i> = startIndex' e 'j

6

suggerirei semplicemente mischiare array invece di cercare di estendere questa a collezioni in generale:

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

C'è un pescatore-Yates shuffle GameKit:

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

Si può anche passare e memorizzare un seme casuale, in modo da ottenere la stessa sequenza di valori di riordino pseudocasuali ogni volta che si fornire lo stesso seme in caso è necessario ricreare una simulazione.

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

Anche osservato qui: http://stackoverflow.com/a/30858350/1187415 :) –

0

È possibile utilizzare l'estensione NSArray dal quadro GameplayKit per questo:

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]