Get random elements from array in Swift Get random elements from array in Swift arrays arrays

Get random elements from array in Swift


Xcode 11 • Swift 5.1

extension Collection {    func choose(_ n: Int) -> ArraySlice<Element> { shuffled().prefix(n) }}

Playground testing

var alphabet = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"]let shuffledAlphabet = alphabet.shuffled()  // "O", "X", "L", "D", "N", "K", "R", "E", "S", "Z", "I", "T", "H", "C", "U", "B", "W", "M", "Q", "Y", "V", "A", "G", "P", "F", "J"]let letter = alphabet.randomElement()  // "D"var numbers = Array(0...9)let shuffledNumbers = numbers.shuffled()shuffledNumbers                              // [8, 9, 3, 6, 0, 1, 4, 2, 5, 7]numbers            // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]numbers.shuffle() // mutate it  [6, 0, 2, 3, 9, 1, 5, 7, 4, 8]numbers            // [6, 0, 2, 3, 9, 1, 5, 7, 4, 8]let pick3numbers = numbers.choose(3)  // [8, 9, 2]

extension RangeReplaceableCollection {    /// Returns a new Collection shuffled    var shuffled: Self { .init(shuffled()) }    /// Shuffles this Collection in place    @discardableResult    mutating func shuffledInPlace() -> Self  {        self = shuffled        return self    }    func choose(_ n: Int) -> SubSequence { shuffled.prefix(n) }}

var alphabetString = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"let shuffledAlphabetString = alphabetString.shuffled  // "DRGXNSJLFQHPUZTBKVMYAWEICO"let character = alphabetString.randomElement()  // "K"alphabetString.shuffledInPlace() // mutate it  "WYQVBLGZKPFUJTHOXERADMCINS"alphabetString            // "WYQVBLGZKPFUJTHOXERADMCINS"let pick3Characters = alphabetString.choose(3)  // "VYA"


Or does anyone have a better/more elegant solution for this?

I do. Algorithmically better than the accepted answer, which does count-1 arc4random_uniform operations for a full shuffle, we can simply pick n values in n arc4random_uniform operations.

And actually, I got two ways of doing better than the accepted answer:

Better solution

extension Array {    /// Picks `n` random elements (straightforward approach)    subscript (randomPick n: Int) -> [Element] {        var indices = [Int](0..<count)        var randoms = [Int]()        for _ in 0..<n {            randoms.append(indices.remove(at: Int(arc4random_uniform(UInt32(indices.count)))))        }        return randoms.map { self[$0] }    }}

Best solution

The following solution is twice faster than previous one.

for Swift 3.0 and 3.1

extension Array {    /// Picks `n` random elements (partial Fisher-Yates shuffle approach)    subscript (randomPick n: Int) -> [Element] {        var copy = self        for i in stride(from: count - 1, to: count - n - 1, by: -1) {            let j = Int(arc4random_uniform(UInt32(i + 1)))            if j != i {                swap(&copy[i], &copy[j])            }        }        return Array(copy.suffix(n))    }}

for Swift 3.2 and 4.x

extension Array {    /// Picks `n` random elements (partial Fisher-Yates shuffle approach)    subscript (randomPick n: Int) -> [Element] {        var copy = self        for i in stride(from: count - 1, to: count - n - 1, by: -1) {            copy.swapAt(i, Int(arc4random_uniform(UInt32(i + 1))))        }        return Array(copy.suffix(n))    }}

Usage:

let digits = Array(0...9)  // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]let pick3digits = digits[randomPick: 3]  // [8, 9, 0]


Swift 4.1 and below

let playlist = ["Nothing Else Matters", "Stairway to Heaven", "I Want to Break Free", "Yesterday"]let index = Int(arc4random_uniform(UInt32(playlist.count)))let song = playlist[index]

Swift 4.2 and above

if let song = playlist.randomElement() {  print(song)} else {  print("Empty playlist.")}