Shuffle Array Swift 3

How do I shuffle an array in Swift?

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]

Shuffle array swift 3

count returns an IndexDistance which is the type describing
the distance between two collection indices. IndexDistance is
required to be a SignedInteger, but need not be an Int and can
be different from Index. Therefore it is not possible to create
the range 0..<count - 1.

A solution is to use startIndex and endIndex instead of 0 and count:

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

Another advantage is that this also works correctly with array slices
(where the index of the first element is not necessarily zero).

Note that according to the new "Swift API Design Guidelines",
shuffle() is the "proper" name for a mutating shuffle method,
and shuffled() for the non-mutating counterpart which returns an array:

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

Update: A (even more general) Swift 3 version has been added to
How do I shuffle an array in Swift? in the meantime.


For Swift 4 (Xcode 9) one has to replace the call to the swap()
function by a call to the swapAt() method of the collection.
Also the restriction on the Index type is no longer needed:

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

See SE-0173 Add MutableCollection.swapAt(_:_:) for more information about swapAt.


As of Swift 4.2 (Xcode 10, currently in beta), with the implementation of
SE-0202 Random Unification,
shuffle() and shuffled() are part of the Swift standard library.

Shuffle/Randomize Questions from an Array with no Repetition (SWIFT)

You seem to be shuffling the list every time you call updateQuestion which seems to be the issue here. You are only supposed to call the shuffle once and iterate through the questions one by one. To fix the issue remove the shuffle from updateQuestion and add it in viewDidLoad or just call it once in updateQuestion based on a condition like this:

if questionNumber == 1 {
allQuestions.list.shuffle()
}

shuffle array after index

shuffle() is a method of the MutableCollection protocol, therefore it can be applied to an array slice. Example:

var arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
arr[5...].shuffle() // Shuffle elements from index 5 to the end
print(arr) // [0, 1, 2, 3, 4, 6, 8, 7, 5, 9]

Shuffle an array with a consistent order, based on today's date - Swift

Something like this?

I looked up how to seed random number generators and found this topic:
Generating random numbers in swift

Using your technique to get todays midnight date as a number... you can seed the number generator and get the same random sequence per midnight date.

import GameplayKit

let myArray = [1, 2, 3, 4, 5]

let calendar = Calendar.current
let todayAtMidnight = calendar.startOfDay(for: Date())
let seed = UInt64(todayAtMidnight.timeIntervalSince1970)

var mersenneTwister = GKMersenneTwisterRandomSource(seed: seed)
let fixedArrayByDate = mersenneTwister.arrayByShufflingObjects(in: myArray)
print(fixedArrayByDate)

This prints out for today's date:

[2, 3, 4, 1, 5]

As suggested by Daniel, one could set up an extension for fun:

import GameplayKit

extension Array {
func shuffledByDate(_ date: Date) -> [Any] {
let calendar = Calendar.current
let todayAtMidnight = calendar.startOfDay(for: date)
let seed = UInt64(todayAtMidnight.timeIntervalSince1970)

let mersenneTwister = GKMersenneTwisterRandomSource(seed: seed)
return mersenneTwister.arrayByShufflingObjects(in: self)
}
}

let myArray = [1, 2, 3, 4, 5].shuffledByDate(Date())
print(myArray)

How do you find this logic in Swift shuffling the elements in an array?

The proposed shuffling algorithm is not correct.

While biases introduced by incorrect shuffling routines are sometimes very hard to discern (it might take large number of monte carlo simulations to reveal, looking at overall statical frequencies), in this case, the resulting problem is self evident. Repeatedly build an array of Array(0..<10), shuffle it, and print the results. The non-random behavior is apparent:

[8, 2, 1, 0, 3, 4, 5, 6, 7, 9]
[8, 1, 0, 2, 3, 4, 5, 6, 7, 9]
[8, 3, 1, 2, 0, 4, 5, 6, 7, 9]
[0, 8, 1, 2, 3, 4, 5, 6, 7, 9]
[8, 3, 1, 2, 0, 4, 5, 6, 7, 9]
[8, 9, 1, 2, 3, 4, 5, 6, 7, 0]
[8, 5, 1, 2, 3, 4, 0, 6, 7, 9]
[8, 4, 1, 2, 3, 0, 5, 6, 7, 9]
[8, 5, 1, 2, 3, 4, 0, 6, 7, 9]
[0, 8, 1, 2, 3, 4, 5, 6, 7, 9]
[8, 5, 1, 2, 3, 4, 0, 6, 7, 9]
[8, 7, 1, 2, 3, 4, 5, 6, 0, 9]
[8, 5, 1, 2, 3, 4, 0, 6, 7, 9]
[8, 2, 1, 0, 3, 4, 5, 6, 7, 9]
[8, 6, 1, 2, 3, 4, 5, 0, 7, 9]
[8, 2, 1, 0, 3, 4, 5, 6, 7, 9]
[0, 8, 1, 2, 3, 4, 5, 6, 7, 9]
[8, 7, 1, 2, 3, 4, 5, 6, 0, 9]
[8, 6, 1, 2, 3, 4, 5, 0, 7, 9]
[8, 7, 1, 2, 3, 4, 5, 6, 0, 9]
[8, 5, 1, 2, 3, 4, 0, 6, 7, 9]
[8, 2, 1, 0, 3, 4, 5, 6, 7, 9]
[8, 4, 1, 2, 3, 0, 5, 6, 7, 9]
[8, 4, 1, 2, 3, 0, 5, 6, 7, 9]
[8, 7, 1, 2, 3, 4, 5, 6, 0, 9]
[8, 5, 1, 2, 3, 4, 0, 6, 7, 9]
[8, 2, 1, 0, 3, 4, 5, 6, 7, 9]
[8, 4, 1, 2, 3, 0, 5, 6, 7, 9]
[8, 7, 1, 2, 3, 4, 5, 6, 0, 9]
[0, 8, 1, 2, 3, 4, 5, 6, 7, 9]

The first serious issue is that given that j is set to 0 on every iteration of i, the while j == i { ... } obviously will not work for any i greater than 0.

Let us imagine that you fixed that issue (by replacing var j = 0 with var j = i), a less obvious issue is that implicit in that while logic is that you're effectively saying that element j must be such that i < j < n whereas the correct logic is i ≤ j < n. Effectively, you're saying that element i must be swapped with another element, whereas in a truly shuffled array, not swapping a particular element at all must be considered as an equally viable option. Frankly, a while statement is not needed, anyway.

In short, it looks like you were attempting to perform a Fisher-Yates. You can replace var j = 0 and the subsequent while loop with a single let statement:

extension Array {
mutating func shuffle2() {
guard count > 1 else { return }

for i in 0 ..< count - 1 {
let j = Int.random(in: i ..< count) // or `Int(arc4random_uniform(UInt32(count - i))) + i`
swapAt(i, j)
}
}
}

Obviously, we would generally use the built in shuffling methods, shuffle, but if writing your own, j can be set with a single statement, without any while loop.

For more information see How do I shuffle an array in Swift?



Related Topics



Leave a reply



Submit