Skip to content

Commit ee68487

Browse files
committed
Add Knuth shuffle algorithm
1 parent 5b73600 commit ee68487

File tree

6 files changed

+217
-1
lines changed

6 files changed

+217
-1
lines changed

README.markdown

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ If you're a computer science student who needs to learn this stuff for exams --
88

99
The goal of this project is to explain how algorithms work. The focus is on clarity and readability of the code, not on making a reusable library that you can drop into your own projects. That said, most of the code should be ready for production use, but you may need to tweak it to fit into your own codebase.
1010

11+
All code is compatible with **Xcode 7.2** and **Swift 2.1**.
12+
1113
This is a work in progress. More algorithms will be added soon. :-)
1214

1315
**Suggestions and contributions are welcome!** Report an issue to leave feedback, or submit a pull request.
@@ -74,7 +76,7 @@ Bad sorting algorithms (don't use these!):
7476

7577
### Miscellaneous
7678

77-
- Shuffle array
79+
- [Shuffle](Shuffle/). Randomly rearranges the contents of an array.
7880

7981
### Mathematics
8082

Shuffle/README.markdown

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# Shuffle
2+
3+
Goal: Rearrange the contents of an array.
4+
5+
Imagine you're making a card game and you need to shuffle a deck of cards. You can represent the deck by an array of `Card` objects and shuffling the deck means to change the order of those objects in the array. (It's like the opposite of sorting.)
6+
7+
Here is a naive way to approach this in Swift:
8+
9+
```swift
10+
extension Array {
11+
public mutating func shuffle() {
12+
var temp = [Element]()
13+
while !isEmpty {
14+
let i = random(count)
15+
let obj = removeAtIndex(i)
16+
temp.append(obj)
17+
}
18+
self = temp
19+
}
20+
}
21+
```
22+
23+
To try it out, copy the code into a playground and then do:
24+
25+
```swift
26+
var list = [ "a", "b", "c", "d", "e", "f", "g" ]
27+
list.shuffle()
28+
list.shuffle()
29+
list.shuffle()
30+
```
31+
32+
You should see three different arrangements -- or permutations to use math-speak -- of the objects in the array.
33+
34+
This shuffle works *in place*, it modifies the contents of the original array. The algorithm works by creating a new array, `temp`, that is initially empty. Then we randomly choose an element from the original array and append it to `temp`, until the original array is empty. Finally, the temporary array is copied back into the original one.
35+
36+
> **Note:** `random()` is a helper function that returns a random integer between 0 and the given maximum.
37+
38+
This code works just fine but it's not very efficient. Removing an element from an array is an **O(n)** operation and we perform this **n** times, making the total algorithm **O(n^2)**. We can do better!
39+
40+
## The Fisher-Yates / Knuth shuffle
41+
42+
Here is a much improved version of the shuffle algorithm:
43+
44+
```swift
45+
extension Array {
46+
public mutating func shuffle() {
47+
for i in (count - 1).stride(through: 1, by: -1) {
48+
let j = random(i + 1)
49+
if i != j {
50+
swap(&self[i], &self[j])
51+
}
52+
}
53+
}
54+
}
55+
```
56+
57+
Again, this picks objects at random. In the naive version we placed those objects into a new temporary array so we could keep track of which objects were already shuffled and which still remained to be done. In this improved algorithm, however, we'll move the shuffled objects to the end of the original array.
58+
59+
> **Note**: When you write `random(x)`, the largest number it will return is `x - 1`. We want to have `j <= i`, not `j < i`, so the largest number from the random number generator needs to be `i`, not `i - 1`. That's why we do `random(i + 1)`. If we didn't add that 1 to compensate, it would make some shuffle orders more likely to occur than others.
60+
61+
Let's walk through the example. We have the array:
62+
63+
[ "a", "b", "c", "d", "e", "f", "g" ]
64+
65+
The loop starts at the end of the array and works its way back to the beginning. The very first random number can be any element from the entire array. Let's say it returns 2, the index of `"c"`. We swap `"c"` with `"g"` to move it to the end:
66+
67+
[ "a", "b", "g", "d", "e", "f" | "c" ]
68+
* *
69+
70+
The array now consists of two regions, indicated by the `|` bar. Everything to the right of the bar is shuffled already.
71+
72+
The next random number is chosen from the range 0...6, so only from the region `[ "a", "b", "g", "d", "e", "f" ]`. It will never choose `"c"` since that object is done and we'll no longer touch it.
73+
74+
Let's say the random number generator picks 0, the index of `"a"`. Then we swap `"a"` with `"f"`, which is the last element in the unshuffled portion, and the array looks like this:
75+
76+
[ "f", "b", "g", "d", "e" | "a", "c" ]
77+
* *
78+
79+
The next random number is somewhere in `[ "f", "b", "g", "d", "e" ]`, so let's say it is 3. We swap `"d"` with `"e"`:
80+
81+
[ "f", "b", "g", "e" | "d", "a", "c" ]
82+
* *
83+
84+
And so on... This continues until there is only one element remaining in the left portion. For example:
85+
86+
[ "b" | "e", "f", "g", "d", "a", "c" ]
87+
88+
There's nothing left to swap that `"b"` with, so we're done.
89+
90+
Because we only look at each array element once, this algorithm has a guaranteed running time of **O(n)**. It's as fast as you could hope to get!
91+
92+
## Creating a new array that is shuffled
93+
94+
There is a slight variation on this algorithm that is useful for when you want to create a new array instance that contains the values `0` to `n-1` in random order.
95+
96+
Here is the code:
97+
98+
```swift
99+
public func shuffledArray(n: Int) -> [Int] {
100+
var a = [Int](count: n, repeatedValue: 0)
101+
for i in 0..<n {
102+
let j = random(i + 1)
103+
if i != j {
104+
a[i] = a[j]
105+
}
106+
a[j] = i
107+
}
108+
return a
109+
}
110+
```
111+
112+
To use it:
113+
114+
```swift
115+
let numbers = shuffledArray(10)
116+
```
117+
118+
This returns something like `[3, 0, 9, 1, 8, 5, 2, 6, 7, 4]`. As you can see, every number between 0 and 10 is in that list, but shuffled around. Of course, when you try it for yourself the order of the numbers will be different.
119+
120+
The `shuffledArray()` function first creates a new array with `n` zeros. Then it loops `n` times and in each step adds the next number from the sequence to a random position in the array. The trick is to make sure that none of these numbers gets overwritten with the next one, so it moves the previous number out of the way first!
121+
122+
The algoritm is quite clever and I suggest you walk through an example yourself, either on paper or in the playground. (Hint: Again it splits the array into two regions.)
123+
124+
## See also
125+
126+
These Swift implementations are based on pseudocode from the [Wikipedia article](https://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
127+
128+
*Written for Swift Algorithm Club by Matthijs Hollemans*
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//: Playground - noun: a place where people can play
2+
3+
import Foundation
4+
5+
/* Returns a random integer between 0 and n-1. */
6+
public func random(n: Int) -> Int {
7+
return Int(arc4random_uniform(UInt32(n)))
8+
}
9+
10+
11+
12+
/* Fisher-Yates / Knuth shuffle */
13+
extension Array {
14+
public mutating func shuffle() {
15+
for i in (count - 1).stride(through: 1, by: -1) {
16+
let j = random(i + 1)
17+
if i != j {
18+
swap(&self[i], &self[j])
19+
}
20+
}
21+
}
22+
}
23+
24+
var list = [ "a", "b", "c", "d", "e", "f", "g" ]
25+
list.shuffle()
26+
list.shuffle()
27+
list.shuffle()
28+
29+
30+
31+
/* Create a new array of numbers that is already shuffled. */
32+
public func shuffledArray(n: Int) -> [Int] {
33+
var a = [Int](count: n, repeatedValue: 0)
34+
for i in 0..<n {
35+
let j = random(i + 1)
36+
if i != j {
37+
a[i] = a[j]
38+
}
39+
a[j] = i
40+
}
41+
return a
42+
}
43+
44+
let numbers = shuffledArray(10)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<playground version='5.0' target-platform='osx'>
3+
<timeline fileName='timeline.xctimeline'/>
4+
</playground>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Timeline
3+
version = "3.0">
4+
<TimelineItems>
5+
</TimelineItems>
6+
</Timeline>

Shuffle/Shuffle.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import Foundation
2+
3+
extension Array {
4+
/*
5+
Randomly shuffles the array in-place
6+
This is the Fisher-Yates algorithm, also known as the Knuth shuffle.
7+
Time complexity: O(n)
8+
*/
9+
public mutating func shuffle() {
10+
for i in (count - 1).stride(through: 1, by: -1) {
11+
let j = random(i + 1)
12+
if i != j {
13+
swap(&self[i], &self[j])
14+
}
15+
}
16+
}
17+
}
18+
19+
/*
20+
Simultaneously initializes an array with the values 0...n-1 and shuffles it.
21+
*/
22+
public func shuffledArray(n: Int) -> [Int] {
23+
var a = [Int](count: n, repeatedValue: 0)
24+
for i in 0..<n {
25+
let j = random(i + 1)
26+
if i != j {
27+
a[i] = a[j]
28+
}
29+
a[j] = i // insert next number from the sequence
30+
}
31+
return a
32+
}

0 commit comments

Comments
 (0)