Randomize Two Arrays the Same Way Swift

Randomize two arrays the same way Swift

Using the shuffle() method from How do I shuffle an array in Swift? and the ideas from How can I sort multiple arrays based on the sorted order of another array
you can shuffle the array indices and then re-order both (or more)
arrays accordingly:

let a = [1, 2, 3, 4]
let b = ["a", "b", "c", "d"]

var shuffled_indices = a.indices.shuffle()

let shuffled_a = Array(PermutationGenerator(elements: a, indices: shuffled_indices))
let shuffled_b = Array(PermutationGenerator(elements: b, indices: shuffled_indices))

print(shuffled_a) // [3, 1, 2, 4]
print(shuffled_b) // ["c", "a", "b", "d"]

Update for Swift 3 (Xcode 8): PermutationGenerator does not
exist in Swift 3 anymore.
Using the shuffled() method
from Shuffle array swift 3 the same can be achieved with

var shuffled_indices = a.indices.shuffled()

let shuffled_a = shuffled_indices.map { a[$0] }
let shuffled_b = shuffled_indices.map { b[$0] }

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.

How to avoid generating the same random String twice in a row in Swift?

Since you only care about not repeating the value as the next value, just remember the previous value and reject the next value if it is the same as the previous value.

Here's a playground example. First, we try this the simplest way:

let pep = ["manny", "moe", "jack"]

func fetchRandomPep() -> String {
return pep.randomElement()!
}

for _ in 1...100 {
let result = fetchRandomPep()
print(result)
}

Well, you probably got many repeated results. Let's prevent that by writing our loop in a different way:

var prev = ""
for _ in 1...100 {
var result = fetchRandomPep()
while result == prev {
result = fetchRandomPep()
}
prev = result
print(result)
}

Now there are no repetitions!

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


Related Topics



Leave a reply



Submit