2016-06-15 5 views
25

Как я могу преобразовать функцию, указанную ниже, в swift 3? В настоящее время возникает ошибка 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]) 
    } 
    } 
} 

ссылка: https://stackoverflow.com/a/24029847/5222077

+0

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

+0

Возможный дубликат [Как перетасовать массив в Swift?] (https://stackoverflow.com/questions/24026510/how-do-i-shuffle-an-array-in-swift) –

+0

Вопрос устарел, поскольку ссылка была обновлена ​​для Swift 3. –

ответ

75

count возвращает IndexDistance, который является тип описания на расстоянии между двумя индексами коллекции. IndexDistance - должен быть SignedInteger, но не обязательно должен быть Int и может быть отличным от Index. Поэтому невозможно создать диапазон 0..<count - 1.

Решение состоит в том, чтобы использовать startIndex и endIndex вместо 0 и 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]) 
      } 
     } 
    } 
} 

Другим преимуществом является то, что это также корректно работает с массивами ломтиками (где индекс первого элемента не обязательно нуль).

Обратите внимание, что в соответствии с новым "Swift API Design Guidelines", shuffle() является "правильным" именем метода Mutating в случайном порядке, и shuffled() для не-мутагенной коллеги, который возвращает массив:

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 (еще более общий) версия Swift 3 была добавлена ​​к How do I shuffle an array in Swift? тем временем.


Для Swift 4 (Xcode 9) один должен заменить вызов функции swap() посредством вызова метода в swapAt() коллекции. не также ограничение на 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) 
     } 
    } 
} 

См SE-0173 Add MutableCollection.swapAt(_:_:) для получения дополнительной информации о swapAt.

+3

Вы гений –

+0

Просто чувствовал, что я должен указать, что '+ i' в' let j', вероятно, должен быть '+ startIndex'. Иначе это, скорее всего, приведет к индексу за пределами. –

+0

@ BjarkeH.Søndergaard: Я уверен, что приведенный выше код верен, и я проверил его как с массивами, так и с массивами. 'i' находится в диапазоне' startIndex .. = i> = startIndex' и 'j

6

Я хотел бы предложить просто перетасовка массивы вместо того, чтобы расширить это в коллекции в целом:

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

Существует рыболов-Yates перетасовать в GameKit:

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

Вы также можете передать и сохранить случайное семя, так что вы получите ту же последовательность значений перетасовки псевдослучайных каждый раз, когда вы поставляете то же семя Если вам нужно воссоздать симуляцию.

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

Также наблюдается здесь: http://stackoverflow.com/a/30858350/1187415 :) –

0

Вы можете использовать NSArray Extension из рамок GameplayKit для этого:

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]