How do I shuffle an array in Swift?

前端 未结 25 2181
长发绾君心
长发绾君心 2020-11-21 05:44

How do I randomize or shuffle the elements within an array in Swift? For example, if my array consists of 52 playing cards, I want to shuffle the array in o

相关标签:
25条回答
  • 2020-11-21 06:21

    It stop at "swap(&self[i], &self[j])" when I upgrade the xCode version to 7.4 beta.
    fatal error: swapping a location with itself is not supported

    I found the reason that i = j (function of swap will exploded)

    So I add a condition as below

    if (i != j){
        swap(&list[i], &list[j])
    }
    

    YA! It's OK for me.

    0 讨论(0)
  • 2020-11-21 06:22

    This answer details how to shuffle with a fast and uniform algorithm (Fisher-Yates) in Swift 4.2+ and how to add the same feature in the various previous versions of Swift. The naming and behavior for each Swift version matches the mutating and nonmutating sorting methods for that version.

    Swift 4.2+

    shuffle and shuffled are native starting Swift 4.2. Example usage:

    let x = [1, 2, 3].shuffled()
    // x == [2, 3, 1]
    
    let fiveStrings = stride(from: 0, through: 100, by: 5).map(String.init).shuffled()
    // fiveStrings == ["20", "45", "70", "30", ...]
    
    var numbers = [1, 2, 3, 4]
    numbers.shuffle()
    // numbers == [3, 2, 1, 4]
    

    Swift 4.0 and 4.1

    These extensions add a shuffle() method to any mutable collection (arrays and unsafe mutable buffers) and a shuffled() method to any sequence:

    extension MutableCollection {
        /// Shuffles the contents of this collection.
        mutating func shuffle() {
            let c = count
            guard c > 1 else { return }
    
            for (firstUnshuffled, unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) {
                // Change `Int` in the next line to `IndexDistance` in < Swift 4.1
                let d: Int = numericCast(arc4random_uniform(numericCast(unshuffledCount)))
                let i = index(firstUnshuffled, offsetBy: d)
                swapAt(firstUnshuffled, i)
            }
        }
    }
    
    extension Sequence {
        /// Returns an array with the contents of this sequence, shuffled.
        func shuffled() -> [Element] {
            var result = Array(self)
            result.shuffle()
            return result
        }
    }
    

    Same usage as in Swift 4.2 examples above.


    Swift 3

    These extensions add a shuffle() method to any mutable collection and a shuffled() method to any sequence:

    extension MutableCollection where Indices.Iterator.Element == Index {
        /// Shuffles the contents of this collection.
        mutating func shuffle() {
            let c = count
            guard c > 1 else { return }
    
            for (firstUnshuffled , unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) {
                // Change `Int` in the next line to `IndexDistance` in < Swift 3.2
                let d: Int = numericCast(arc4random_uniform(numericCast(unshuffledCount)))
                guard d != 0 else { continue }
                let i = index(firstUnshuffled, offsetBy: d)
                self.swapAt(firstUnshuffled, i)
            }
        }
    }
    
    extension Sequence {
        /// Returns an array with the contents of this sequence, shuffled.
        func shuffled() -> [Iterator.Element] {
            var result = Array(self)
            result.shuffle()
            return result
        }
    }
    

    Same usage as in Swift 4.2 examples above.


    Swift 2

    (obsolete language: you can't use Swift 2.x to publish on iTunes Connect starting July 2018)

    extension MutableCollectionType 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 startIndex ..< endIndex - 1 {
                let j = Int(arc4random_uniform(UInt32(count - i))) + i
                guard i != j else { continue }
                swap(&self[i], &self[j])
            }
        }
    }
    
    extension CollectionType {
        /// Return a copy of `self` with its elements shuffled.
        func shuffle() -> [Generator.Element] {
            var list = Array(self)
            list.shuffleInPlace()
            return list
        }
    }
    

    Usage:

    [1, 2, 3].shuffle()
    // [2, 3, 1]
    
    let fiveStrings = 0.stride(through: 100, by: 5).map(String.init).shuffle()
    // ["20", "45", "70", "30", ...]
    
    var numbers = [1, 2, 3, 4]
    numbers.shuffleInPlace()
    // [3, 2, 1, 4]
    

    Swift 1.2

    (obsolete language: you can't use Swift 1.x to publish on iTunes Connect starting July 2018)

    shuffle as a mutating array method

    This extension will let you shuffle a mutable Array instance in place:

    extension Array {
        mutating func shuffle() {
            if count < 2 { return }
            for i in 0..<(count - 1) {
                let j = Int(arc4random_uniform(UInt32(count - i))) + i
                swap(&self[i], &self[j])
            }
        }
    }
    var numbers = [1, 2, 3, 4, 5, 6, 7, 8]
    numbers.shuffle()                     // e.g., numbers == [6, 1, 8, 3, 2, 4, 7, 5]
    

    shuffled as a non-mutating array method

    This extension will let you retrieve a shuffled copy of an Array instance:

    extension Array {
        func shuffled() -> [T] {
            if count < 2 { return self }
            var list = self
            for i in 0..<(list.count - 1) {
                let j = Int(arc4random_uniform(UInt32(list.count - i))) + i
                swap(&list[i], &list[j])
            }
            return list
        }
    }
    let numbers = [1, 2, 3, 4, 5, 6, 7, 8]
    let mixedup = numbers.shuffled()     // e.g., mixedup == [6, 1, 8, 3, 2, 4, 7, 5]
    
    0 讨论(0)
  • 2020-11-21 06:22

    Edit: As noted in other answers, Swift 4.2 finally adds random number generation to the standard library, complete with array shuffling.

    However, the GKRandom / GKRandomDistribution suite in GameplayKit can still be useful with the new RandomNumberGenerator protocol — if you add extensions to the GameplayKit RNGs to conform to the new standard library protocol, you can easily get:

    • sendable RNGs (that can reproduce a "random" sequence when needed for testing)
    • RNGs that sacrifice robustness for speed
    • RNGs that produce non-uniform distributions

    ...and still make use of the nice new "native" random APIs in Swift.

    The rest of this answer concerns such RNGs and/or their use in older Swift compilers.


    There are some good answers here already, as well as some good illustrations of why writing your own shuffle can be error-prone if you're not careful.

    In iOS 9, macOS 10.11, and tvOS 9 (or later), you don't have to write your own. There's an efficient, correct implementation of Fisher-Yates in GameplayKit (which, despite the name, is not just for games).

    If you just want a unique shuffle:

    let shuffled = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: array)
    

    If you want to be able to replicate a shuffle or series of shuffles, choose and seed a specific random source; e.g.

    let lcg = GKLinearCongruentialRandomSource(seed: mySeedValue)
    let shuffled = lcg.arrayByShufflingObjects(in: array)
    

    In iOS 10 / macOS 10.12 / tvOS 10, there's also a convenience syntax for shuffling via an extension on NSArray. Of course, that's a little cumbersome when you're using a Swift Array (and it loses its element type on coming back to Swift):

    let shuffled1 = (array as NSArray).shuffled(using: random) // -> [Any]
    let shuffled2 = (array as NSArray).shuffled() // use default random source
    

    But it's pretty easy to make a type-preserving Swift wrapper for it:

    extension Array {
        func shuffled(using source: GKRandomSource) -> [Element] {
            return (self as NSArray).shuffled(using: source) as! [Element]
        }
        func shuffled() -> [Element] {
            return (self as NSArray).shuffled() as! [Element]
        }
    }
    let shuffled3 = array.shuffled(using: random)
    let shuffled4 = array.shuffled()
    
    0 讨论(0)
  • 2020-11-21 06:26

    As of swift 4.2 there are two handy functions:

    // shuffles the array in place
    myArray.shuffle()
    

    and

    // generates a new array with shuffled elements of the old array
    let newArray = myArray.shuffled()
    
    0 讨论(0)
  • 2020-11-21 06:27

    This is what I use:

    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 讨论(0)
  • 2020-11-21 06:28

    With Swift 3, if you want to shuffle an array in place or get a new shuffled array from an array, AnyIterator can help you. The idea is to create an array of indices from your array, to shuffle those indices with an AnyIterator instance and swap(_:_:) function and to map each element of this AnyIterator instance with the array's corresponding element.


    The following Playground code shows how it works:

    import Darwin // required for arc4random_uniform
    
    let array = ["Jock", "Ellie", "Sue Ellen", "Bobby", "JR", "Pamela"]
    var indexArray = Array(array.indices)
    var index = indexArray.endIndex
    
    let indexIterator: AnyIterator<Int> = AnyIterator {
        guard let nextIndex = indexArray.index(index, offsetBy: -1, limitedBy: indexArray.startIndex)
            else { return nil }
    
        index = nextIndex
        let randomIndex = Int(arc4random_uniform(UInt32(index)))
        if randomIndex != index {
            swap(&indexArray[randomIndex], &indexArray[index])
        }
    
        return indexArray[index]
    }
    
    let newArray = indexIterator.map { array[$0] }
    print(newArray) // may print: ["Jock", "Ellie", "Sue Ellen", "JR", "Pamela", "Bobby"]
    

    You can refactor the previous code and create a shuffled() function inside an Array extension in order to get a new shuffled array from an array:

    import Darwin // required for arc4random_uniform
    
    extension Array {
    
        func shuffled() -> Array<Element> {
            var indexArray = Array<Int>(indices)        
            var index = indexArray.endIndex
    
            let indexIterator = AnyIterator<Int> {
                guard let nextIndex = indexArray.index(index, offsetBy: -1, limitedBy: indexArray.startIndex)
                    else { return nil }
    
                index = nextIndex                
                let randomIndex = Int(arc4random_uniform(UInt32(index)))
                if randomIndex != index {
                    swap(&indexArray[randomIndex], &indexArray[index])
                }
    
                return indexArray[index]
            }
    
            return indexIterator.map { self[$0] }
        }
    
    }
    

    Usage:

    let array = ["Jock", "Ellie", "Sue Ellen", "Bobby", "JR", "Pamela"]
    let newArray = array.shuffled()
    print(newArray) // may print: ["Bobby", "Pamela", "Jock", "Ellie", "JR", "Sue Ellen"]
    
    let emptyArray = [String]()
    let newEmptyArray = emptyArray.shuffled()
    print(newEmptyArray) // prints: []
    

    As an alternative to the previous code, you can create a shuffle() function inside an Array extension in order to shuffle an array in place:

    import Darwin // required for arc4random_uniform
    
    extension Array {
    
        mutating func shuffle() {
            var indexArray = Array<Int>(indices)
            var index = indexArray.endIndex
    
            let indexIterator = AnyIterator<Int> {
                guard let nextIndex = indexArray.index(index, offsetBy: -1, limitedBy: indexArray.startIndex)
                    else { return nil }
    
                index = nextIndex                
                let randomIndex = Int(arc4random_uniform(UInt32(index)))
                if randomIndex != index {
                    swap(&indexArray[randomIndex], &indexArray[index])
                }
    
                return indexArray[index]
            }
    
            self = indexIterator.map { self[$0] }
        }
    
    }
    

    Usage:

    var mutatingArray = ["Jock", "Ellie", "Sue Ellen", "Bobby", "JR", "Pamela"]
    mutatingArray.shuffle()
    print(mutatingArray) // may print ["Sue Ellen", "Pamela", "Jock", "Ellie", "Bobby", "JR"]
    
    0 讨论(0)
提交回复
热议问题