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?
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:
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] }
}
}
The following solution is twice faster than previous one.
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(©[i], ©[j])
}
}
return Array(copy.suffix(n))
}
}
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))
}
}
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.")
}
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With