Get random elements from array in swift

2019-01-03 03:09发布

I have an array like:

var names: String = [ "Peter", "Steve", "Max", "Sandra", "Roman", "Julia" ]

I would like to get 3 random elements from that array. I'm coming from C# but in swift I'm unsure where to start. I think I should shuffle the array first and then pick the first 3 items from it for example?

I tried to shuffle it with the following extension:

extension Array
{
    mutating func shuffle()
    {
        for _ in 0..<10
        {
            sort { (_,_) in arc4random() < arc4random() }
        }
    }
}

but it then says "'()' is not convertible to '[Int]'" at the location of "shuffle()".

For picking a number of elements i use:

var randomPicks = names[0..<4];

which looks good so far.

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

5条回答
淡お忘
2楼-- · 2019-01-03 03:38

You could define an extension on Array:

extension Array {
    func pick(_ n: Int) -> [Element] {
        guard count >= n else {
            fatalError("The count has to be at least \(n)")
        }
        guard n >= 0 else {
            fatalError("The number of elements to be picked must be positive")
        }

        let shuffledIndices = indices.shuffled().prefix(upTo: n)
        return shuffledIndices.map {self[$0]}
    }
}

[ "Peter", "Steve", "Max", "Sandra", "Roman", "Julia" ].pick(3)

If the initial array may have duplicates, and you want uniqueness in value:

extension Array where Element: Hashable {
    func pickUniqueInValue(_ n: Int) -> [Element] {
        let set: Set<Element> = Set(self)
        guard set.count >= n else {
            fatalError("The array has to have at least \(n) unique values")
        }
        guard n >= 0 else {
            fatalError("The number of elements to be picked must be positive")
        }

        return Array(set.prefix(upTo: set.index(set.startIndex, offsetBy: n)))
    }
}

[ "Peter", "Steve", "Max", "Sandra", "Roman", "Julia" ].pickUniqueInValue(3)
查看更多
\"骚年 ilove
3楼-- · 2019-01-03 03:45

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.")
}
查看更多
虎瘦雄心在
4楼-- · 2019-01-03 03:46

Xcode 9 • Swift 4

extension Array {
    /// Returns an array containing this sequence shuffled
    var shuffled: Array {
        var elements = self
        return elements.shuffle()
    }
    /// Shuffles this sequence in place
    @discardableResult
    mutating func shuffle() -> Array {
        let count = self.count
        indices.lazy.dropLast().forEach {
            swapAt($0, Int(arc4random_uniform(UInt32(count - $0))) + $0)
        }
        return self
    }
    var chooseOne: Element { return self[Int(arc4random_uniform(UInt32(count)))] }
    func choose(_ n: Int) -> Array { return Array(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

let letter = alphabet.chooseOne

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]

Xcode 8.3.1 • Swift 3.1

import UIKit

extension Array {
    /// Returns an array containing this sequence shuffled
    var shuffled: Array {
        var elements = self
        return elements.shuffle()
    }
    /// Shuffles this sequence in place
    @discardableResult
    mutating func shuffle() -> Array {
        let count = self.count
        indices.lazy.dropLast().forEach {
            guard case let index = Int(arc4random_uniform(UInt32(count - $0))) + $0, index != $0 else { return }
            swap(&self[$0], &self[index])
        }
        return self
    }
    var chooseOne: Element { return self[Int(arc4random_uniform(UInt32(count)))] }
    func choose(_ n: Int) -> Array { return Array(shuffled.prefix(n)) }
}
查看更多
贼婆χ
5楼-- · 2019-01-03 03:56

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]
查看更多
【Aperson】
6楼-- · 2019-01-03 03:56

You could also use arc4random() to just choose three elements from the Array. Something like this:

extension Array {
    func getRandomElements() -> (T, T, T) {
        return (self[Int(arc4random()) % Int(count)],
                self[Int(arc4random()) % Int(count)],
                self[Int(arc4random()) % Int(count)])
    }
}

let names = ["Peter", "Steve", "Max", "Sandra", "Roman", "Julia"]
names.getRandomElements()

This is just an example, you could also include logic in the function to get a different name for each one.

查看更多
登录 后发表回答