2016-06-15 19 views
25

Jak przekonwertować poniższą funkcję na swift 3? Obecnie dostaję błąd 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]) 
    } 
    } 
} 

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

+0

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

+0

Możliwy duplikat [Jak mogę przetasować tablicę w Swift?] (https://stackoverflow.com/questions/24026510/how-do-i-shuffle-an-array-in-swift) –

+0

Pytanie jest przestarzałe, ponieważ odniesienie zostało zaktualizowane dla Swift 3. –

Odpowiedz

75

count zwraca IndexDistance który typ opisujący podaną odległość między dwoma wskaźnikami kolekcji. IndexDistance jest wymagane, aby być SignedInteger, ale nie musi być Int i może różnić się od Index. Dlatego nie można utworzyć w zakresie 0..<count - 1.

Rozwiązaniem jest użycie startIndex i endIndex zamiast 0 i 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]) 
      } 
     } 
    } 
} 

Kolejną zaletą jest to, że także działa prawidłowo tablicy plastry (gdzie wskaźnik na pierwszy element jest koniecznie zero).

Należy zauważyć, że zgodnie z nowym "Swift API Design Guidelines", shuffle() jest "właściwe" nazwa metody Mutating shuffle, i shuffled() o braku mutacji odpowiednik która zwraca tablicę:

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

Update: A (jeszcze bardziej ogólna) Wersja Swift 3 została dodana w międzyczasie do How do I shuffle an array in Swift?.


Na Swift Xcode 4 (9) trzeba wymienić wywołanie funkcji swap() przez wywołanie metody zbierania swapAt(). także ograniczenie typu Index nie jest już potrzebne:

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

Zobacz SE-0173 Add MutableCollection.swapAt(_:_:) więcej informacji o swapAt.

+3

Jesteś geniuszem –

+0

Po prostu poczułem, że powinienem zaznaczyć, że '+ i' w' let j' prawdopodobnie powinien być '+ startIndex' zamiast tego. W przeciwnym razie bardzo prawdopodobne jest, że wskaźnik zniknie. –

+0

@ BjarkeH.Søndergaard: Jestem całkiem pewien, że powyższy kod jest poprawny i przetestowałem go zarówno z tablicami, jak i tablicami. 'i' jest w zakresie' startIndex .. = i> = startIndex' oraz 'j

6

Proponuję po prostu tasowania tablic zamiast próbować przedłużyć ten do zbiorów ogólnie:

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

Jest Fisher-Yates przetasowań GameKit:

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

Można także przekazać i przechowywać losowych, więc masz tę samą sekwencję wartości pseudolosowych losowego odtwarzania za każdym razem podać ten sam materiał siewny w przypadku musisz odtworzyć symulację.

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

Zaobserwowano również tutaj: http://stackoverflow.com/a/30858350/1187415 :) –

0

Można użyć NSArray Extension z ram GameplayKit na to:

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]