web-dev-qa-db-ja.com

配列をシャッフルSwift 3

以下の関数をSwift 3に変換するにはどうすればよいですか?現在、Binary operator '..<' cannot be applied to operands of type 'Int' and 'Self.IndexDistance'エラーが発生しています。

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

29
kye

countは、2つのコレクションインデックス間の距離を表すタイプであるIndexDistanceを返します。 IndexDistanceSignedIntegerである必要がありますが、Intである必要はなく、Indexと異なっていてもかまいません。したがって、範囲_0..<count - 1_を作成することはできません。

解決策は、_0_とstartIndexの代わりにendIndexcountを使用することです。

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

もう1つの利点は、配列slices(最初の要素のインデックスが必ずしもゼロではない場合)でも正しく機能することです。

新しい "Swift API設計ガイドライン" によれば、shuffle()は変更シャッフルメソッドの「適切な」名前であり、shuffled()はnon配列を返す-mutatingの対応物:

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

更新:A(さらに一般的)Swift 3バージョンが追加されました Swiftで配列をシャッフルするにはどうすればよいですか? その間。


Swift 4(Xcode 9)の場合、swap()関数の呼び出しをswapAt()コレクションのメソッド。また、Index型の制限は不要になりました。

_extension MutableCollection {
    /// Shuffle the elements of `self` in-place.
    mutating func shuffle() {
        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)
        }
    }
}
_

swapAtの詳細については、 SE-0173 Add MutableCollection.swapAt(_:_:) を参照してください。


Swift 4.2(Xcode 10、現在ベータ版)、 SE-0202 Random Unificationshuffle()およびshuffled()は、Swift標準ライブラリの一部です。

78
Martin R

Gamekitにはfisher-yatesシャッフルがあります。

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)
10
Josh Homann

これを一般的なコレクションに拡張するのではなく、単に配列をシャッフルすることをお勧めします。

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])
        }
    }
}
8
matt

これには、GameplayKitフレームワークのNSArray Extensionを使用できます。

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]
0
Daniel Illescas